diff --git a/.beads/backup/backup_state.json b/.beads/backup/backup_state.json index 02e9020c5..30433d222 100644 --- a/.beads/backup/backup_state.json +++ b/.beads/backup/backup_state.json @@ -1,13 +1,13 @@ { - "last_dolt_commit": "upf2aiitle3ggkfmkkko7fqf0ggc6d08", + "last_dolt_commit": "i2kkcuigo62e2gslamqigpe536igpqc6", "last_event_id": 0, - "timestamp": "2026-03-07T20:17:17.755332Z", + "timestamp": "2026-03-07T23:46:55.891384Z", "counts": { - "issues": 2189, - "events": 17963, + "issues": 2213, + "events": 18073, "comments": 3400, - "dependencies": 1573, - "labels": 2753, + "dependencies": 1594, + "labels": 2825, "config": 11 } } \ No newline at end of file diff --git a/.beads/backup/dependencies.jsonl b/.beads/backup/dependencies.jsonl index 3c2ab11b4..930d89a83 100644 --- a/.beads/backup/dependencies.jsonl +++ b/.beads/backup/dependencies.jsonl @@ -24,6 +24,7 @@ {"created_at":"2026-02-07T09:09:56Z","created_by":"Dan Abrams","depends_on_id":"gromit-2a6","issue_id":"gromit-0uq","type":"blocks"} {"created_at":"2026-02-07T09:09:56Z","created_by":"Dan Abrams","depends_on_id":"gromit-use","issue_id":"gromit-0uq","type":"blocks"} {"created_at":"2026-02-05T19:59:50Z","created_by":"Dan Abrams","depends_on_id":"gromit-d25","issue_id":"gromit-0x2","type":"blocks"} +{"created_at":"2026-03-07T15:41:51Z","created_by":"Dan Abrams","depends_on_id":"gromit-zrrn7","issue_id":"gromit-0xxdf","type":"blocks"} {"created_at":"2026-02-16T01:30:33Z","created_by":"Dan Abrams","depends_on_id":"gromit-c4uk","issue_id":"gromit-0zuz","type":"blocks"} {"created_at":"2026-02-06T05:20:37Z","created_by":"Dan Abrams","depends_on_id":"gromit-1w0","issue_id":"gromit-109","type":"blocks"} {"created_at":"2026-02-06T05:20:37Z","created_by":"Dan Abrams","depends_on_id":"gromit-1xd","issue_id":"gromit-109","type":"blocks"} @@ -45,6 +46,7 @@ {"created_at":"2026-02-23T02:10:46Z","created_by":"Dan Abrams","depends_on_id":"gromit-18sei","issue_id":"gromit-18sei.1","type":"parent-child"} {"created_at":"2026-02-23T02:10:47Z","created_by":"Dan Abrams","depends_on_id":"gromit-18sei","issue_id":"gromit-18sei.2","type":"parent-child"} {"created_at":"2026-02-23T02:10:48Z","created_by":"Dan Abrams","depends_on_id":"gromit-18sei","issue_id":"gromit-18sei.3","type":"parent-child"} +{"created_at":"2026-03-07T15:41:51Z","created_by":"Dan Abrams","depends_on_id":"gromit-ew1l8","issue_id":"gromit-1b6ks","type":"blocks"} {"created_at":"2026-02-19T03:21:06Z","created_by":"Dan Abrams","depends_on_id":"gromit-8kn5h","issue_id":"gromit-1c2ii","type":"blocks"} {"created_at":"2026-02-19T03:21:07Z","created_by":"Dan Abrams","depends_on_id":"gromit-9wmf9","issue_id":"gromit-1c2ii","type":"blocks"} {"created_at":"2026-02-19T03:21:08Z","created_by":"Dan Abrams","depends_on_id":"gromit-gbxmn","issue_id":"gromit-1c2ii","type":"blocks"} @@ -55,6 +57,7 @@ {"created_at":"2026-02-19T18:49:12Z","created_by":"Dan Abrams","depends_on_id":"gromit-70enc","issue_id":"gromit-1dimb","type":"blocks"} {"created_at":"2026-02-19T18:49:13Z","created_by":"Dan Abrams","depends_on_id":"gromit-gyylv","issue_id":"gromit-1dimb","type":"blocks"} {"created_at":"2026-02-23T09:02:19Z","created_by":"Dan Abrams","depends_on_id":"gromit-r7lcc","issue_id":"gromit-1fjzj","type":"blocks"} +{"created_at":"2026-03-07T15:41:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-v1j2u","issue_id":"gromit-1fuf8","type":"blocks"} {"created_at":"2026-02-19T18:24:25Z","created_by":"Dan Abrams","depends_on_id":"gromit-odihv","issue_id":"gromit-1fuo5","type":"blocks"} {"created_at":"2026-02-05T20:02:03Z","created_by":"Dan Abrams","depends_on_id":"gromit-2t6","issue_id":"gromit-1i7","type":"blocks"} {"created_at":"2026-03-07T06:41:46Z","created_by":"Dan Abrams","depends_on_id":"gromit-kweh3","issue_id":"gromit-1o1h5","type":"blocks"} @@ -225,6 +228,9 @@ {"created_at":"2026-02-20T03:39:31Z","created_by":"Dan Abrams","depends_on_id":"gromit-61692","issue_id":"gromit-61692.1","type":"parent-child"} {"created_at":"2026-02-20T03:39:31Z","created_by":"Dan Abrams","depends_on_id":"gromit-61692","issue_id":"gromit-61692.2","type":"parent-child"} {"created_at":"2026-02-20T03:39:32Z","created_by":"Dan Abrams","depends_on_id":"gromit-61692","issue_id":"gromit-61692.3","type":"parent-child"} +{"created_at":"2026-03-07T15:41:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-8yarv","issue_id":"gromit-61cbs","type":"blocks"} +{"created_at":"2026-03-07T15:41:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-9z0j1","issue_id":"gromit-61cbs","type":"blocks"} +{"created_at":"2026-03-07T15:41:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-f4ouz","issue_id":"gromit-61cbs","type":"blocks"} {"created_at":"2026-02-07T09:09:56Z","created_by":"Dan Abrams","depends_on_id":"gromit-2a6","issue_id":"gromit-65k","type":"blocks"} {"created_at":"2026-02-07T09:09:56Z","created_by":"Dan Abrams","depends_on_id":"gromit-jba","issue_id":"gromit-65k","type":"blocks"} {"created_at":"2026-02-07T09:09:56Z","created_by":"Dan Abrams","depends_on_id":"gromit-n73","issue_id":"gromit-65k","type":"blocks"} @@ -484,6 +490,8 @@ {"created_at":"2026-02-21T15:04:42Z","created_by":"Dan Abrams","depends_on_id":"gromit-ufopy","issue_id":"gromit-8te5t","type":"blocks"} {"created_at":"2026-02-21T15:04:44Z","created_by":"Dan Abrams","depends_on_id":"gromit-z57ht","issue_id":"gromit-8te5t","type":"blocks"} {"created_at":"2026-02-07T23:53:16Z","created_by":"Dan Abrams","depends_on_id":"gromit-8nfq","issue_id":"gromit-8tnl","type":"blocks"} +{"created_at":"2026-03-07T15:41:51Z","created_by":"Dan Abrams","depends_on_id":"gromit-9z0j1","issue_id":"gromit-8yarv","type":"blocks"} +{"created_at":"2026-03-07T15:41:51Z","created_by":"Dan Abrams","depends_on_id":"gromit-f4ouz","issue_id":"gromit-8yarv","type":"blocks"} {"created_at":"2026-02-07T08:38:38Z","created_by":"Dan Abrams","depends_on_id":"gromit-2bm","issue_id":"gromit-8yl","type":"blocks"} {"created_at":"2026-02-07T08:38:38Z","created_by":"Dan Abrams","depends_on_id":"gromit-bkk","issue_id":"gromit-8yl","type":"blocks"} {"created_at":"2026-02-22T22:17:44Z","created_by":"Dan Abrams","depends_on_id":"gromit-gg2fy","issue_id":"gromit-92gfw","type":"blocks"} @@ -522,6 +530,7 @@ {"created_at":"2026-02-20T15:36:42Z","created_by":"Dan Abrams","depends_on_id":"gromit-dhpfx","issue_id":"gromit-9wx1w","type":"blocks"} {"created_at":"2026-02-19T19:42:40Z","created_by":"Dan Abrams","depends_on_id":"gromit-kyknq","issue_id":"gromit-9xmm1","type":"blocks"} {"created_at":"2026-02-19T19:42:40Z","created_by":"Dan Abrams","depends_on_id":"gromit-retjh","issue_id":"gromit-9xmm1","type":"blocks"} +{"created_at":"2026-03-07T15:41:51Z","created_by":"Dan Abrams","depends_on_id":"gromit-f4ouz","issue_id":"gromit-9z0j1","type":"blocks"} {"created_at":"2026-02-07T08:38:38Z","created_by":"Dan Abrams","depends_on_id":"gromit-def","issue_id":"gromit-a1s","type":"blocks"} {"created_at":"2026-02-07T05:59:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-b3s","issue_id":"gromit-a9g","type":"blocks"} {"created_at":"2026-02-07T05:59:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-c7z","issue_id":"gromit-a9g","type":"blocks"} @@ -547,6 +556,7 @@ {"created_at":"2026-02-13T11:34:16Z","created_by":"Dan Abrams","depends_on_id":"gromit-wy62","issue_id":"gromit-aqye","type":"blocks"} {"created_at":"2026-02-13T11:34:16Z","created_by":"Dan Abrams","depends_on_id":"gromit-yhvc","issue_id":"gromit-aqye","type":"blocks"} {"created_at":"2026-02-22T17:18:05Z","created_by":"Dan Abrams","depends_on_id":"gromit-n2xw8","issue_id":"gromit-atgpc","type":"blocks"} +{"created_at":"2026-03-07T15:41:49Z","created_by":"Dan Abrams","depends_on_id":"gromit-ew1l8","issue_id":"gromit-atr10","type":"blocks"} {"created_at":"2026-02-07T23:32:16Z","created_by":"Dan Abrams","depends_on_id":"gromit-6x11","issue_id":"gromit-avbc","type":"blocks"} {"created_at":"2026-02-07T23:32:16Z","created_by":"Dan Abrams","depends_on_id":"gromit-pzh0","issue_id":"gromit-avbc","type":"blocks"} {"created_at":"2026-02-12T13:55:07Z","created_by":"Dan Abrams","depends_on_id":"gromit-tmc1","issue_id":"gromit-awh9","type":"blocks"} @@ -620,12 +630,17 @@ {"created_at":"2026-02-22T14:03:42Z","created_by":"Dan Abrams","depends_on_id":"gromit-cr50o","issue_id":"gromit-cr50o.3","type":"parent-child"} {"created_at":"2026-02-18T16:25:43Z","created_by":"Dan Abrams","depends_on_id":"gromit-9qy4","issue_id":"gromit-ctgu","type":"blocks"} {"created_at":"2026-02-07T22:39:04Z","created_by":"Dan Abrams","depends_on_id":"gromit-9ckj","issue_id":"gromit-ctpu","type":"blocks"} +{"created_at":"2026-03-07T15:41:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-20962","issue_id":"gromit-cufx0","type":"blocks"} +{"created_at":"2026-03-07T15:41:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-7a6rt","issue_id":"gromit-cufx0","type":"blocks"} {"created_at":"2026-02-22T22:17:22Z","created_by":"Dan Abrams","depends_on_id":"gromit-wa18r","issue_id":"gromit-cvl1y","type":"blocks"} {"created_at":"2026-02-22T12:04:57Z","created_by":"Dan Abrams","depends_on_id":"gromit-cxfim","issue_id":"gromit-cwk4z","type":"blocks"} {"created_at":"2026-02-22T12:04:56Z","created_by":"Dan Abrams","depends_on_id":"gromit-jetjq","issue_id":"gromit-cwk4z","type":"blocks"} {"created_at":"2026-02-07T04:50:46Z","created_by":"Dan Abrams","depends_on_id":"gromit-d6y","issue_id":"gromit-cxt","type":"blocks"} {"created_at":"2026-02-07T04:50:46Z","created_by":"Dan Abrams","depends_on_id":"gromit-ej8","issue_id":"gromit-cxt","type":"blocks"} {"created_at":"2026-02-11T09:27:33Z","created_by":"Dan Abrams","depends_on_id":"gromit-7oob","issue_id":"gromit-cz7a","type":"blocks"} +{"created_at":"2026-03-07T15:41:50Z","created_by":"Dan Abrams","depends_on_id":"gromit-ew1l8","issue_id":"gromit-d2rpx","type":"blocks"} +{"created_at":"2026-03-07T15:41:49Z","created_by":"Dan Abrams","depends_on_id":"gromit-jv6ns","issue_id":"gromit-d2rpx","type":"blocks"} +{"created_at":"2026-03-07T15:41:51Z","created_by":"Dan Abrams","depends_on_id":"gromit-atr10","issue_id":"gromit-d4134","type":"blocks"} {"created_at":"2026-02-11T07:42:16Z","created_by":"Dan Abrams","depends_on_id":"gromit-vflg","issue_id":"gromit-d46r","type":"blocks"} {"created_at":"2026-02-07T05:47:06Z","created_by":"Dan Abrams","depends_on_id":"gromit-r8c","issue_id":"gromit-d92","type":"blocks"} {"created_at":"2026-02-06T22:43:29Z","created_by":"Dan Abrams","depends_on_id":"gromit-twl","issue_id":"gromit-da0","type":"parent-child"} @@ -746,6 +761,7 @@ {"created_at":"2026-02-18T18:04:54Z","created_by":"Dan Abrams","depends_on_id":"gromit-gzzq5","issue_id":"gromit-etfv9","type":"blocks"} {"created_at":"2026-02-18T18:04:54Z","created_by":"Dan Abrams","depends_on_id":"gromit-usaw1","issue_id":"gromit-etfv9","type":"blocks"} {"created_at":"2026-02-15T20:17:36Z","created_by":"Dan Abrams","depends_on_id":"gromit-fnqk","issue_id":"gromit-evne","type":"blocks"} +{"created_at":"2026-03-07T15:41:49Z","created_by":"Dan Abrams","depends_on_id":"gromit-jv6ns","issue_id":"gromit-ew1l8","type":"blocks"} {"created_at":"2026-02-18T20:49:32Z","created_by":"Dan Abrams","depends_on_id":"gromit-tdsdn","issue_id":"gromit-ewhf4","type":"blocks"} {"created_at":"2026-02-07T05:43:45Z","created_by":"Dan Abrams","depends_on_id":"gromit-eiv","issue_id":"gromit-ewl","type":"blocks"} {"created_at":"2026-02-06T10:01:00Z","created_by":"Dan Abrams","depends_on_id":"gromit-ex7","issue_id":"gromit-eww","type":"blocks"} @@ -1356,6 +1372,7 @@ {"created_at":"2026-02-19T03:21:13Z","created_by":"Dan Abrams","depends_on_id":"gromit-8kn5h","issue_id":"gromit-u2pch","type":"blocks"} {"created_at":"2026-02-19T03:21:14Z","created_by":"Dan Abrams","depends_on_id":"gromit-aftvs","issue_id":"gromit-u2pch","type":"blocks"} {"created_at":"2026-02-12T17:55:41Z","created_by":"Dan Abrams","depends_on_id":"gromit-slll","issue_id":"gromit-u2zx","type":"blocks"} +{"created_at":"2026-03-07T15:41:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-1b6ks","issue_id":"gromit-u4837","type":"blocks"} {"created_at":"2026-02-19T19:42:48Z","created_by":"Dan Abrams","depends_on_id":"gromit-3la54","issue_id":"gromit-u4aqq","type":"blocks"} {"created_at":"2026-02-19T19:42:48Z","created_by":"Dan Abrams","depends_on_id":"gromit-4fhlk","issue_id":"gromit-u4aqq","type":"blocks"} {"created_at":"2026-02-19T19:42:48Z","created_by":"Dan Abrams","depends_on_id":"gromit-75zeo","issue_id":"gromit-u4aqq","type":"blocks"} @@ -1391,6 +1408,7 @@ {"created_at":"2026-02-22T06:34:50Z","created_by":"Dan Abrams","depends_on_id":"gromit-ks39d","issue_id":"gromit-uslyx","type":"blocks"} {"created_at":"2026-03-07T00:55:34Z","created_by":"Dan Abrams","depends_on_id":"gromit-8ztwd","issue_id":"gromit-utelc","type":"blocks"} {"created_at":"2026-03-07T00:55:34Z","created_by":"Dan Abrams","depends_on_id":"gromit-gc3wz","issue_id":"gromit-utelc","type":"blocks"} +{"created_at":"2026-03-07T15:41:51Z","created_by":"Dan Abrams","depends_on_id":"gromit-jzbe3","issue_id":"gromit-uulh1","type":"blocks"} {"created_at":"2026-02-18T16:58:05Z","created_by":"Dan Abrams","depends_on_id":"gromit-5k3v","issue_id":"gromit-uwag","type":"blocks"} {"created_at":"2026-02-08T08:16:05Z","created_by":"Dan Abrams","depends_on_id":"gromit-sliw","issue_id":"gromit-uwyu","type":"blocks"} {"created_at":"2026-02-19T18:33:01Z","created_by":"Dan Abrams","depends_on_id":"gromit-47mxi","issue_id":"gromit-uxug5","type":"blocks"} @@ -1401,6 +1419,7 @@ {"created_at":"2026-02-23T02:05:35Z","created_by":"Dan Abrams","depends_on_id":"gromit-uzrhu","issue_id":"gromit-uzrhu.1","type":"parent-child"} {"created_at":"2026-02-23T02:05:36Z","created_by":"Dan Abrams","depends_on_id":"gromit-uzrhu","issue_id":"gromit-uzrhu.2","type":"parent-child"} {"created_at":"2026-02-23T02:05:38Z","created_by":"Dan Abrams","depends_on_id":"gromit-uzrhu","issue_id":"gromit-uzrhu.3","type":"parent-child"} +{"created_at":"2026-03-07T15:41:52Z","created_by":"Dan Abrams","depends_on_id":"gromit-fhp2q","issue_id":"gromit-v46qz","type":"blocks"} {"created_at":"2026-02-24T04:25:14Z","created_by":"Dan Abrams","depends_on_id":"gromit-gmigz","issue_id":"gromit-v6q0g","type":"blocks"} {"created_at":"2026-02-07T23:32:16Z","created_by":"Dan Abrams","depends_on_id":"gromit-pzh0","issue_id":"gromit-v7ft","type":"blocks"} {"created_at":"2026-02-15T21:27:31Z","created_by":"Dan Abrams","depends_on_id":"gromit-ap30","issue_id":"gromit-va54","type":"blocks"} @@ -1429,6 +1448,8 @@ {"created_at":"2026-02-16T18:12:00Z","created_by":"Dan Abrams","depends_on_id":"gromit-ixf7","issue_id":"gromit-w0z6","type":"blocks"} {"created_at":"2026-02-16T18:12:00Z","created_by":"Dan Abrams","depends_on_id":"gromit-o9ix","issue_id":"gromit-w0z6","type":"blocks"} {"created_at":"2026-02-16T18:12:00Z","created_by":"Dan Abrams","depends_on_id":"gromit-xqjz","issue_id":"gromit-w0z6","type":"blocks"} +{"created_at":"2026-03-07T15:41:49Z","created_by":"Dan Abrams","depends_on_id":"gromit-ew1l8","issue_id":"gromit-w1200","type":"blocks"} +{"created_at":"2026-03-07T15:41:49Z","created_by":"Dan Abrams","depends_on_id":"gromit-jzbe3","issue_id":"gromit-w1200","type":"blocks"} {"created_at":"2026-02-18T17:50:15Z","created_by":"Dan Abrams","depends_on_id":"gromit-85z2x","issue_id":"gromit-w23y4","type":"blocks"} {"created_at":"2026-02-06T08:12:15Z","created_by":"Dan Abrams","depends_on_id":"gromit-a2j","issue_id":"gromit-w2k","type":"parent-child"} {"created_at":"2026-02-19T18:30:45Z","created_by":"Dan Abrams","depends_on_id":"gromit-kcszq","issue_id":"gromit-w3cwr","type":"blocks"} diff --git a/.beads/backup/events.jsonl b/.beads/backup/events.jsonl index 4848e074e..65a837796 100644 --- a/.beads/backup/events.jsonl +++ b/.beads/backup/events.jsonl @@ -17961,3 +17961,113 @@ {"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T13:47:36Z","event_type":"closed","id":17961,"issue_id":"gromit-o1fk9","new_value":"Closed","old_value":""} {"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T14:00:57Z","event_type":"closed","id":17962,"issue_id":"gromit-yhg13","new_value":"Closed","old_value":""} {"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:17:17Z","event_type":"closed","id":17963,"issue_id":"gromit-b582a","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:29:33Z","event_type":"closed","id":17964,"issue_id":"gromit-cbn9c","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:33:06Z","event_type":"closed","id":17965,"issue_id":"gromit-x5abw","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:49Z","event_type":"created","id":17966,"issue_id":"gromit-jv6ns","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17967,"issue_id":"gromit-jv6ns","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17968,"issue_id":"gromit-jv6ns","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17969,"issue_id":"gromit-jv6ns","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:49Z","event_type":"created","id":17970,"issue_id":"gromit-ew1l8","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17971,"issue_id":"gromit-ew1l8","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17972,"issue_id":"gromit-ew1l8","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:3","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17973,"issue_id":"gromit-ew1l8","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:49Z","event_type":"created","id":17974,"issue_id":"gromit-jzbe3","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17975,"issue_id":"gromit-jzbe3","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17976,"issue_id":"gromit-jzbe3","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17977,"issue_id":"gromit-jzbe3","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:49Z","event_type":"created","id":17978,"issue_id":"gromit-w1200","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17979,"issue_id":"gromit-w1200","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17980,"issue_id":"gromit-w1200","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17981,"issue_id":"gromit-w1200","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:49Z","event_type":"created","id":17982,"issue_id":"gromit-atr10","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17983,"issue_id":"gromit-atr10","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17984,"issue_id":"gromit-atr10","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17985,"issue_id":"gromit-atr10","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:49Z","event_type":"created","id":17986,"issue_id":"gromit-d2rpx","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17987,"issue_id":"gromit-d2rpx","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17988,"issue_id":"gromit-d2rpx","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:49Z","event_type":"label_added","id":17989,"issue_id":"gromit-d2rpx","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:50Z","event_type":"created","id":17990,"issue_id":"gromit-w83pd","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":17991,"issue_id":"gromit-w83pd","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":17992,"issue_id":"gromit-w83pd","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":17993,"issue_id":"gromit-w83pd","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:50Z","event_type":"created","id":17994,"issue_id":"gromit-zrrn7","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":17995,"issue_id":"gromit-zrrn7","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":17996,"issue_id":"gromit-zrrn7","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":17997,"issue_id":"gromit-zrrn7","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:50Z","event_type":"created","id":17998,"issue_id":"gromit-20962","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":17999,"issue_id":"gromit-20962","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18000,"issue_id":"gromit-20962","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:3","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18001,"issue_id":"gromit-20962","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:50Z","event_type":"created","id":18002,"issue_id":"gromit-7a6rt","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18003,"issue_id":"gromit-7a6rt","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18004,"issue_id":"gromit-7a6rt","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18005,"issue_id":"gromit-7a6rt","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:50Z","event_type":"created","id":18006,"issue_id":"gromit-fhp2q","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18007,"issue_id":"gromit-fhp2q","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18008,"issue_id":"gromit-fhp2q","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18009,"issue_id":"gromit-fhp2q","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:50Z","event_type":"created","id":18010,"issue_id":"gromit-v1j2u","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18011,"issue_id":"gromit-v1j2u","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18012,"issue_id":"gromit-v1j2u","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18013,"issue_id":"gromit-v1j2u","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:50Z","event_type":"created","id":18014,"issue_id":"gromit-f4ouz","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18015,"issue_id":"gromit-f4ouz","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18016,"issue_id":"gromit-f4ouz","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:50Z","event_type":"label_added","id":18017,"issue_id":"gromit-f4ouz","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:51Z","event_type":"created","id":18018,"issue_id":"gromit-9z0j1","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18019,"issue_id":"gromit-9z0j1","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18020,"issue_id":"gromit-9z0j1","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18021,"issue_id":"gromit-9z0j1","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:51Z","event_type":"created","id":18022,"issue_id":"gromit-8yarv","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18023,"issue_id":"gromit-8yarv","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18024,"issue_id":"gromit-8yarv","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18025,"issue_id":"gromit-8yarv","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:51Z","event_type":"created","id":18026,"issue_id":"gromit-1b6ks","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18027,"issue_id":"gromit-1b6ks","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18028,"issue_id":"gromit-1b6ks","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18029,"issue_id":"gromit-1b6ks","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:51Z","event_type":"created","id":18030,"issue_id":"gromit-uulh1","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18031,"issue_id":"gromit-uulh1","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18032,"issue_id":"gromit-uulh1","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18033,"issue_id":"gromit-uulh1","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:51Z","event_type":"created","id":18034,"issue_id":"gromit-d4134","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18035,"issue_id":"gromit-d4134","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18036,"issue_id":"gromit-d4134","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18037,"issue_id":"gromit-d4134","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:51Z","event_type":"created","id":18038,"issue_id":"gromit-0xxdf","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18039,"issue_id":"gromit-0xxdf","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18040,"issue_id":"gromit-0xxdf","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:51Z","event_type":"label_added","id":18041,"issue_id":"gromit-0xxdf","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:52Z","event_type":"created","id":18042,"issue_id":"gromit-cufx0","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18043,"issue_id":"gromit-cufx0","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18044,"issue_id":"gromit-cufx0","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18045,"issue_id":"gromit-cufx0","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:52Z","event_type":"created","id":18046,"issue_id":"gromit-v46qz","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18047,"issue_id":"gromit-v46qz","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18048,"issue_id":"gromit-v46qz","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18049,"issue_id":"gromit-v46qz","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:52Z","event_type":"created","id":18050,"issue_id":"gromit-1fuf8","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18051,"issue_id":"gromit-1fuf8","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18052,"issue_id":"gromit-1fuf8","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18053,"issue_id":"gromit-1fuf8","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:52Z","event_type":"created","id":18054,"issue_id":"gromit-61cbs","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18055,"issue_id":"gromit-61cbs","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18056,"issue_id":"gromit-61cbs","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18057,"issue_id":"gromit-61cbs","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:41:52Z","event_type":"created","id":18058,"issue_id":"gromit-u4837","new_value":"","old_value":""} +{"actor":"Dan Abrams","comment":"Added label: spec:v2-loop-routing-and-resume","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18059,"issue_id":"gromit-u4837","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: gen:0","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18060,"issue_id":"gromit-u4837","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":"Added label: estimated-files:2","created_at":"2026-03-07T15:41:52Z","event_type":"label_added","id":18061,"issue_id":"gromit-u4837","new_value":null,"old_value":null} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:46:29Z","event_type":"closed","id":18062,"issue_id":"gromit-20962","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T15:49:50Z","event_type":"closed","id":18063,"issue_id":"gromit-7a6rt","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:04:07Z","event_type":"closed","id":18064,"issue_id":"gromit-cufx0","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:09:43Z","event_type":"closed","id":18065,"issue_id":"gromit-f4ouz","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:17:46Z","event_type":"closed","id":18066,"issue_id":"gromit-fhp2q","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:23:30Z","event_type":"closed","id":18067,"issue_id":"gromit-jv6ns","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:26:46Z","event_type":"closed","id":18068,"issue_id":"gromit-jzbe3","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:28:52Z","event_type":"closed","id":18069,"issue_id":"gromit-uulh1","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:32:29Z","event_type":"closed","id":18070,"issue_id":"gromit-v1j2u","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:36:59Z","event_type":"closed","id":18071,"issue_id":"gromit-v46qz","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:40:03Z","event_type":"closed","id":18072,"issue_id":"gromit-w83pd","new_value":"Closed","old_value":""} +{"actor":"Dan Abrams","comment":null,"created_at":"2026-03-07T16:45:12Z","event_type":"closed","id":18073,"issue_id":"gromit-zrrn7","new_value":"Closed","old_value":""} diff --git a/.beads/backup/issues.jsonl b/.beads/backup/issues.jsonl index f28865073..f84840106 100644 --- a/.beads/backup/issues.jsonl +++ b/.beads/backup/issues.jsonl @@ -40,6 +40,7 @@ {"acceptance_criteria":"EnsureWorktree creates worktree at \u003cproject\u003e-gromit-interactive sibling path\nEnsureWorktree reuses existing healthy worktree without recreating\nCreateBranch creates correctly-named branch in the worktree","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-14T15:12:53Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"be5768483eb647082edb3bf904c86fff16cab30603be202f2f4276fee868dbe8","created_at":"2026-02-12T16:38:33Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/worktree/worktree.go with Manager struct containing MainDir, WorktreeDir, and gitRunFn fields. Implement NewManager, EnsureWorktree (creates or verifies worktree at sibling path), CreateBranch (generates gromit/\u003ccommand\u003e-\u003ctimestamp\u003e branch), and Cleanup (removes worktree). Wire gitRunFn as field for testability.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-0vxy","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create worktree.Manager with lifecycle methods","updated_at":"2026-02-14T15:12:53Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- [ ] Function IsScopeTooLarge(output string) (bool, string) added\n- [ ] Returns true + breakdown text when output contains 'SCOPE_TOO_LARGE:' prefix\n- [ ] Returns false + empty string when marker not present\n- [ ] go build ./... passes","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-05T21:35:57Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"9169e43f690f26f4ef12f1e9fcab2bb6ad571fce87acadb0d1c8ebca272e13f6","created_at":"2026-02-05T19:59:41Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add a function that checks Claude's output for the SCOPE_TOO_LARGE: prefix marker and extracts the suggested breakdown text. Similar to existing VALIDATION_PASSED/FAILED detection. File: internal/claude/claude.go","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-0x2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add IsScopeTooLarge function to claude package","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Experiment struct matches spec JSON format with all required fields\nBaselineMetrics struct has avg cost, duration, token, and failure rate fields","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T00:49:06Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"46ce4b2036c9614ecc437746cfa24e6a68b523cfce2d7817555e1c14abb40608","created_at":"2026-02-07T23:32:16Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/retro/experiment.go with Experiment struct containing Name, Hypothesis, Change, Measurement, Risk, StartedAt, and BaselineMetrics fields. Define BaselineMetrics with fields for average cost, duration, tokens, and failure rate. Include JSON tags.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-0xbs","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Define Experiment and BaselineMetrics structs","updated_at":"2026-02-08T00:49:06Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"393749dddc4bff60e8f66b131a2576dd27f484efa83a33d43314fe7a47c831cd","created_at":"2026-03-07T20:41:52Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add tests to internal/v2/gap/gap_test.go: verify gap analysis diffs file correctly, identifies beads with file changes, does not flag beads with no overlap.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-0xxdf","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Add unit tests for gap analysis","updated_at":"2026-03-07T20:41:52Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"retro_filter_test.go is deleted\nstdin_helper_example_test.go is deleted\nUseful test ideas captured via gromit add before deletion\ngo test ./... passes","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-11T08:18:26Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c771b638299f84920e1bc684fa1a27fbd17c77783b33aa728c9429b6a7e65041","created_at":"2026-02-11T05:25:39Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"internal/retro/retro_filter_test.go has 14 tests ALL with t.Skip() — every single test is a placeholder that never runs. cmd/gromit/stdin_helper_example_test.go has 2 permanently-skipped example tests. Per RULES.md: 'Do not write tests with t.Skip() for scenarios that cannot run in the test environment.' Capture useful behaviors in backlog via gromit add, then delete both files.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-0ypa","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Delete permanently-skipped tests in retro_filter_test.go and stdin_helper_example_test.go","updated_at":"2026-02-11T08:18:26Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Config uses haiku model for all priorities and validation\nEscalation enabled with chain [haiku, sonnet]\nValidation enabled with go test ./... command","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T00:02:19Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"cfe5e65435562e0c752aa04e568ed49dee1b5f4dbe1a2e26dcffc1e6a9c16d81","created_at":"2026-02-06T22:43:30Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create test/e2e/testdata/gromit.yaml with minimal configuration for fast E2E tests. Use haiku for all models, enable escalation with haiku→sonnet chain, enable validation with go test command.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-0zg","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create E2E test config file","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"1. Skill outputs spec file to .gromit/specs/\u003cname\u003e.md (not beads)\n2. Keeps one-question-at-a-time conversational approach\n3. Includes codebase exploration phase before designing\n4. Includes collaborative spec naming (propose slug, human approves)\n5. Spec format uses frontmatter (id, source_ideas, created) with four sections: Specification, Acceptance Criteria, Decisions, Research \u0026 Context\n6. No bd create guidance or bead creation references remain","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T13:53:39Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"45d3541924470d4e23ec20047021fc4986c57ee4bb993faad14a61a8f44bae69","created_at":"2026-02-06T13:41:30Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Rewrite skills/gromit-refine/SKILL.md to produce specs instead of beads. Keep one-question-at-a-time approach and conversational methodology. Output: spec file with frontmatter (id, source_ideas, created) and four sections (Specification, Acceptance Criteria, Decisions, Research \u0026 Context). Add codebase exploration phase. Remove bd create guidance.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-0zh","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Rewrite gromit-refine skill for spec output","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} @@ -66,10 +67,12 @@ {"acceptance_criteria":"internal/pipeline/decompose_test.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T02:38:47Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"df7c28c25224e971413fa1508bd0f5338c2cecde9561301f572d9e8cc8620529","created_at":"2026-02-23T02:10:49Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-18sei.3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Extend decompose tests for per-attempt summary, final warning details, and clean-exit no-warning behavior","updated_at":"2026-02-23T02:38:47Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-19T02:17:29Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"07db2cc4467082c009b36528acd510a2097072892bdb8364fb66e11b3882308b","created_at":"2026-02-19T00:57:54Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"All 130 Codex iterations report $0 cost_usd. Root causes: (1) estimatedCostUSD() uses 'openai' as config key but runtime provider name is 'codex', (2) per-model pricing was commented out in gromit.yaml. Fix: uncomment gromit.yaml pricing section, fix provider name mapping in cost estimation, add integration test verifying non-zero cost_usd for each configured provider. Leading indicator: next run's JSONL logs show non-zero cost_usd for Codex iterations.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-18wuw","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix Codex cost tracking — provider name mismatch and missing per-model pricing","updated_at":"2026-02-19T02:17:29Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-21T09:54:09Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"93d5b7c6598ac2b1ed9b525c88cfcb39abceaf74f61ea5e472d3b5f2caca48bc","created_at":"2026-02-20T02:14:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"After extracting create and helper functions, internal/bead/bead.go remains above the 500-line production-file guideline. Continue moving cohesive method groups into focused files while preserving behavior and tests.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1ajzr","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Further split internal/bead/bead.go below 500 lines","updated_at":"2026-02-21T09:54:09Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7dc55beadb25693fa7402197684ed4f821a7aa53b6f337f1c81c368dd57b4400","created_at":"2026-03-07T20:41:51Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add tests to internal/v2/routing/router_test.go: verify ratio balancing produces correct provider selection, phase preferences override ratio, fallback marks unavailable and selects alternative, cooldown re-enables provider.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1b6ks","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Add unit tests for Router ratio balancing and fallback","updated_at":"2026-03-07T20:41:51Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T09:41:26Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4541bd9d03c861cf7e690eb34bb09c8166bff653e302f2dbd724913cad6dd69a","created_at":"2026-02-19T03:20:43Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"In internal/runner/process_methodology.go, parse criteria from spec via coverage.ParseCriteria and create a tracker at TDD cycle start. Inject coverage state into red phase prompt before each cycle. After red phase, parse self-report via coverage.ParseSelfReport. After green phase, construct haiku validation callback in internal/runner/callbacks.go and call executor.ValidateCoverage. Call tracker.MarkCovered or tracker.RecordRejection based on response. Use tracker.IsComplete for cycle termination. Add tests.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1c2ii","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Wire CoverageTracker into TDD cycle loop","updated_at":"2026-02-20T09:41:26Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Each gate failure produces a bead with correct spec:\u003cname\u003e label\nBead titles and descriptions reflect the specific failure\nCreation is capped at 5 beads per synthesis call","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T14:36:23Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"155e6ec17c0ec9e99f1354e0a9a06f5f1013c76986f6378a4daf087587e9c15b","created_at":"2026-02-16T01:30:38Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create SynthesizeFixBeads(ctx, specName, failures []GateFailure, beadClient BeadClient) ([]string, error) function in internal/runner/spec_fix.go. For each GateFailure, create a new bead via beadClient.Create() with title derived from failure, description including suggested fix, priority P0, and labels [spec:\u003cname\u003e]. Cap at 5 fix beads per call.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1d0u","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement fix bead synthesis from gate failures","updated_at":"2026-02-20T14:36:23Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-19T23:14:17Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"62500a62064fff49b75fc9d59b7fd6332db48e9ae8c3739f4e83f01d0b0f638d","created_at":"2026-02-19T18:49:07Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add CoverageState string and TargetCriterion string fields to the Context struct in internal/prompt/prompt.go. Modify .gromit/templates/PROMPT_tdd_build.md to add a conditional section after the task description that renders coverage state when present: {{if .CoverageState}}## Coverage State\\n{{.CoverageState}}{{end}}. Add tests in internal/prompt/prompt_test.go verifying conditional rendering both when fields are populated and when empty.\\n\\nAcceptance criteria:\\n- prompt.Context has CoverageState and TargetCriterion string fields\\n- TDD build template renders the coverage state section when CoverageState is non-empty\\n- Template renders identically to before when coverage fields are empty strings","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1dimb","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add coverage state fields to prompt.Context and TDD build template","updated_at":"2026-02-19T23:14:17Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"CreateSessionWorktree retry behavior follows gromit-r7lcc contract exactly\\nTests cover branch exists, worktree exists, non-retryable failures, and ambiguous text cases\\nErrors returned after retry exhaustion preserve root-cause context","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T09:24:35Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"6ab9d75312b9fdf9e0465056861fac2c064fed320f0465022847b9a31fcbb4ea","created_at":"2026-02-23T09:02:20Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Implement the contract from gromit-r7lcc in internal/worktree. Replace fragile string-only matching with the agreed classifier strategy, preserve retry semantics, and expand tests to cover mixed retryable/non-retryable failures and ambiguous messages.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1fjzj","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement session worktree contention classifier from approved contract","updated_at":"2026-02-23T09:24:35Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"950f87653e618656bee07405c9e982b58123138853f3d06b2a54d3e86ed1faa3","created_at":"2026-03-07T20:41:52Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add tests to internal/v2/loop/bead_loop_test.go: blocked bead deferred to end of pass, blocked bead proceeds after dependency completes, all-blocked pass terminates with descriptive error.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1fuf8","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Add integration tests for gate re-queue on block","updated_at":"2026-03-07T20:41:52Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"spec_orchestrator.go rewritten in pipeline refactor — new implementation uses runArgv by default","closed_at":"2026-02-21T14:30:01Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"33e1f2dd8cd0b58a306eec2147c196e69e26c0c27a4fc566d0c2258c1ae0ddc6","created_at":"2026-02-19T18:23:47Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Replace runCmd git add and git commit calls in internal/runner/spec_orchestrator.go with o.runArgv using explicit argv vectors. The git add pathspec :(glob)**/*_acceptance_test.go passes as a separate arg without shell quoting. Add tests verifying the correct argv vectors.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1fuo5","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"Experiment: demoted P1→P2 (haiku). Mechanical: migrate to runArgv. Tracking tier-demotion-2026-02-20.","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":4,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Migrate spec_orchestrator.go git operations to runArgv","updated_at":"2026-02-21T14:30:01Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T01:31:38Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a33c5958647ca4bc6c68cc94c71bd8fcc00efdad227d2e440d8bef6d51c300fd","created_at":"2026-02-22T22:00:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Scope-gate decomposition currently trusts model output shape beyond JSON parsing. Add strict contract checks (2-5 sub-beads, \u003c=5 expected outputs per child, no empty/duplicate outputs, no exact parent echo) and explicit fallback behavior when constraints are violated.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1gnya","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Enforce decomposition output quality contract in gate path","updated_at":"2026-02-23T01:31:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-05T04:23:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"ef15a8436c0c976c4f2734e08ee9f5e7ac91b117cca7d8a0b1b1682313973982","created_at":"2026-03-03T21:55:27Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify cmd/gromit/tui.go to change runTui() into a for-loop: call HydrateStore including pipeline items at the top of each iteration, create and run tea.Program capturing the returned Model, then break the loop when model.PendingAction is nil.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1h6qk","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement exit/relaunch loop in runTui","updated_at":"2026-03-05T04:23:07Z","waiters":"","wisp_type":"","work_type":""} @@ -104,6 +107,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:31Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"805840eafc46f1e05a50fd15e321fcd4fca606ee426fe3cbef00e9c2ef7527d4","created_at":"2026-02-20T00:10:44Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Agent resolver adapters (cliAgentResolver, agentResolverAdapter, exploreAgentResolver) are copy-pasted across 3+ cmd/gromit files. Interface changes require O(n) updates. Extract a shared agents.NewResolver(cfg) factory function so command files use it instead of per-command adapter structs. Leading indicator: zero adapter struct definitions in cmd/gromit/*.go files.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1yx73","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Extract shared agentResolver adapter into internal/agents/ package","updated_at":"2026-03-02T19:42:31Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T13:27:16Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a22e4edb8b197f758fca3b70562b64f2911befcba0cf2d9cc8361d71fc3a1a56","created_at":"2026-03-07T11:41:47Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add squasher func(context.Context) error field to present.Stage, create WithSquasher option function, and update New to accept variadic options. Call squasher in Run before invoking the presenter. Include unit tests verifying squasher is called, errors propagate, and nil-check is backward compatible.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1zcnn","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add squasher hook to Present stage","updated_at":"2026-03-07T13:27:16Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T11:21:22Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b81481d46a956377bbd5e7848911af8ef9e8f8aada90bf56cbde51fc1a9adb4a","created_at":"2026-02-06T09:30:00Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add review config section to defaultConfig in cmd/ralph/init.go. Defaults to enabled: false for both light and thorough reviews so existing users are not surprised. See docs/plans/2026-02-06-review-system-plan.md Task 16.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-1zx","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update default ralph.yaml config with review section","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T20:46:29Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"551db7a489321115e25ce0329a61f401b21806aebf43f7469f5c014622e7a1ee","created_at":"2026-03-07T20:41:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/remediation/ (or equivalent) runner contract: change Run() signature to Run(ctx, specID, worktree string) error. Update Request template to populate worktree field for all remediation stages.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-20962","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Expand remediation runner signature for worktree propagation","updated_at":"2026-03-07T20:46:29Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"All adapter methods return typed structs instead of maps\nNo runtime type assertions in any adapter\ngo build ./... compiles successfully","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-14T09:37:37Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5be66c2a06068a11195fa17b27e05a3467425cec22d903e66d5bb5a7c22ead4b","created_at":"2026-02-12T15:20:57Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify cmd/gromit/decompose.go and review.go to update claudeClientAdapter, cliClaudeClient, beadClientAdapter, cliBeadClient, and cliPromptRenderer to use typed signatures. Remove all map construction and type assertions from adapters.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-20iy","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update CLI adapters to use typed signatures","updated_at":"2026-02-14T09:37:37Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Enable Runner construction without Claude in deps","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:28Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c75a609800fbc5e30f31450b15f456bd1e569168e6c9f5188a65574fd29e484b","created_at":"2026-02-19T14:47:13Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Runner refactor target: Deps/ NewRunnerWithDeps should support Router-only dependencies (no Claude field) and instantiate without fallback-to-Claude behavior.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-20mha","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Enable Runner construction without Claude in deps","updated_at":"2026-03-02T19:42:28Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"CheckStaleness returns true with reason for clean_exit: false or stale timestamp\nCheckStaleness treats zero UpdatedAt as stale regardless of threshold\nAutoHeal resets counters but preserves LastReviewCommit and LastRetro","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T00:06:42Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"19a063c3f89d4723a5b49ed4940c7e3b6463058b4421adfa375aa3657a4cd65f","created_at":"2026-02-07T22:39:04Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add SetCleanExit(bool), CheckStaleness(thresholdMinutes int) (bool, string), and AutoHeal() methods to File in internal/state/state.go. CheckStaleness detects crash (CleanExit false) or stale timestamp (UpdatedAt older than threshold). AutoHeal resets IterationsSinceReview and LastReviewIteration to 0 while preserving LastReviewCommit and LastRetro.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-20v4","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement staleness detection and auto-heal methods","updated_at":"2026-02-08T00:06:42Z","waiters":"","wisp_type":"","work_type":""} @@ -336,6 +340,7 @@ {"acceptance_criteria":"`Deps` includes an `ArgvRunner` field of type `ArgvRunnerFn`.\n`newRunnerWithDepsImpl` sets `r.argvRunnerFn` from deps when provided.\n`newRunnerWithDepsImpl` uses `defaultArgvRunner` when `deps.ArgvRunner` is nil.","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T04:04:05Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"ec404562615104b576f7c38dcd27369d824c98f1ce42107763f9f4be829e712a","created_at":"2026-02-20T03:39:31Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Extend `Deps` in `constructor_with_deps.go` with `ArgvRunner ArgvRunnerFn` and update `newRunnerWithDepsImpl` so `r.argvRunnerFn` is assigned from `deps.ArgvRunner`, falling back to `defaultArgvRunner` when nil.\n\nAcceptance criteria:\n- `Deps` includes an `ArgvRunner` field of type `ArgvRunnerFn`.\n- `newRunnerWithDepsImpl` sets `r.argvRunnerFn` from deps when provided.\n- `newRunnerWithDepsImpl` uses `defaultArgvRunner` when `deps.ArgvRunner` is nil.\n","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-61692.1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add ArgvRunner to Deps and runner wiring","updated_at":"2026-02-20T04:04:05Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"`SpecOrchestrator` has an `argvRunnerFn` field.\n`SpecOrchestrator.runArgv` calls the configured `argvRunnerFn`.\n`SpecOrchestrator.runArgv` falls back to `defaultArgvRunner` if no runner is set.","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T04:16:10Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b7dec31ecc59f0e32855432f0b8172629d5053a1c92bf90e87f452ee81afaf96","created_at":"2026-02-20T03:39:32Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add `argvRunnerFn ArgvRunnerFn` to `SpecOrchestrator` and implement a `runArgv` method that executes via that function, using `defaultArgvRunner` when the orchestrator’s runner function is nil.\n\nAcceptance criteria:\n- `SpecOrchestrator` has an `argvRunnerFn` field.\n- `SpecOrchestrator.runArgv` calls the configured `argvRunnerFn`.\n- `SpecOrchestrator.runArgv` falls back to `defaultArgvRunner` if no runner is set.\n","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-61692.2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add argv runner support to SpecOrchestrator","updated_at":"2026-02-20T04:16:10Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"SpecOrchestrator is constructed with the runner’s configured argv runner function.\nA non-nil `deps.ArgvRunner` is observable inside SpecOrchestrator execution flow.\nBuild/tests covering orchestrator construction and argv execution pass with both custom and default runner paths.","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T04:21:09Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4a881c268b791182ee072b069e91f5943fa557a65d09167112edf2855e52212e","created_at":"2026-02-20T03:39:33Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Update SpecOrchestrator construction paths to pass through the runner’s configured argv runner (`r.argvRunnerFn`) so orchestration uses injected behavior end-to-end.\n\nAcceptance criteria:\n- SpecOrchestrator is constructed with the runner’s configured argv runner function.\n- A non-nil `deps.ArgvRunner` is observable inside SpecOrchestrator execution flow.\n- Build/tests covering orchestrator construction and argv execution pass with both custom and default runner paths.\n","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-61692.3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Thread argv runner into SpecOrchestrator creation","updated_at":"2026-02-20T04:21:09Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"74e3d10246a61213954a6a8b012477c3858a56e1423a9f74663f83c7ea01ea5b","created_at":"2026-03-07T20:41:52Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add tests to internal/v2/prompt/assembler_test.go: phase filtering retains correct sections with cap respected, CLAUDE.md scoping to package relevant sections, budget shaping respects total cap with scope adjustment.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-61cbs","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Add unit tests for phase filtering and scoping in prompt assembler","updated_at":"2026-03-07T20:41:52Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:31Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"64a8b98cb0178ccdd3b4e7811101c3bcc1beaefb921fc72550e752827c0d29a7","created_at":"2026-02-20T01:38:17Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Agent resolver adapters (cliAgentResolver, agentResolverAdapter, exploreAgentResolver) are copy-pasted across cmd/gromit files. Extract into a single shared implementation so interface changes touch 1 file instead of 3+. See retro 2026-02-20 system_actions.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-61qv7","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":3,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Extract shared agent resolver adapter in cmd/gromit","updated_at":"2026-03-02T19:42:31Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-05T10:49:39Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b4bf77ca7abc7d56cb9355ce3ec5297f774de2b5c68d681043995d6346e9f272","created_at":"2026-03-05T09:57:26Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"In cmd/gromit/adapters.go, trackerItemToBeadInfo() silently discards errors from json.Unmarshal for labels and fmt.Sscanf for priority. Also json.Marshal errors are discarded in Create(). Add proper error handling or at minimum log warnings.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-62ro2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix unchecked JSON/Sscanf errors in adapters.go trackerItemToBeadInfo","updated_at":"2026-03-05T10:49:39Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"HasOpenChildren runs bd list --json --status open --parent \u003cid\u003e --limit 1 instead of fetching all open beads\nReturns true when bd returns non-empty array, false when empty\nExisting validation (nil client, invalid parentID) still works","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-14T07:42:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"0a4579d12bc8f04aae2956f895b2d10c00bbe822ba11990ac642e89d269c9d97","created_at":"2026-02-12T15:15:10Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Replace HasOpenChildren implementation in internal/bead/bead.go at line 689 to use targeted query: bd list --json --status open --parent \u003cid\u003e --limit 1 instead of fetching all open beads client-side. Parse output and return true if non-empty array. Add unit tests in bead_test.go to verify command arguments and return values.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-6426","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Optimize HasOpenChildren to use --parent flag with tests","updated_at":"2026-02-14T07:42:07Z","waiters":"","wisp_type":"","work_type":""} @@ -408,6 +413,7 @@ {"acceptance_criteria":"internal/procutil/procutil_test.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-03-04T18:58:57Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"faa658ddd7a015c08262352d2dd3c0452b786e6b00fcd55388ae70c58957f31e","created_at":"2026-03-04T18:02:42Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-791l5.1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Write failing tests for GOMAXPROCS-respecting SubprocessEnv behavior","updated_at":"2026-03-04T18:58:57Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"internal/procutil/procutil.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-03-04T18:20:26Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e9e973e5d97f81f8b403e8e8f5ab5a53faff2058300c78bac0f92740439af7f3","created_at":"2026-03-04T18:02:43Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-791l5.2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update SubprocessEnv to skip override when GOMAXPROCS is already set, and add comment documenting cgroup/macOS context","updated_at":"2026-03-04T18:20:26Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-03-04T01:54:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"12cc0c83c9d6ed8a36a89170508faf22280cae8819fff8c3e23911534625f52f","created_at":"2026-03-03T21:55:25Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Extend the HydrationProvider interface in internal/tui/hydration.go with a PipelineItems(ctx, gromitDir, specsDir, plansDir) (PipelineItems, error) method. Update HydrateStore() to call this method and populate the Store, logging a warning on error instead of returning it.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-79kjl","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Extend HydrationProvider and HydrateStore for pipeline data","updated_at":"2026-03-04T01:54:20Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T20:49:50Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"8e6c998ef47283a9d9d3f15c22cf03d42007f313555e9b19769f0f5ce57f14bd","created_at":"2026-03-07T20:41:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/v2/loop/topo.go: implement topological sort of beads by DependsOn/BlockedBy graph. Beads with no dependencies come first. Stable tie-breaking for deterministic ordering.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-7a6rt","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement topological sort for beads","updated_at":"2026-03-07T20:49:50Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"--agent flag selects the specified agent for refine\n--choose-agent flag shows the interactive picker\nDefault behavior (no flags, no agents config) launches claude identically to current behavior","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-09T07:19:21Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c752173bf9b4a316240c69003f768b7c75879e371a529daf1e32f63e7476c597","created_at":"2026-02-08T09:09:44Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add --agent (string) and --choose-agent (bool) flags to the refine cobra command in cmd/gromit/refine.go. After writing the prompt to the temp file, call agent.Resolve() to get the agent, then call agent.Launch(promptPath). Remove the inline Claude binary/flags resolution and exec.Command construction. Keep prompt building and post-exit artifact detection unchanged.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-7dcu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Wire agent selection into refine command","updated_at":"2026-02-09T07:19:21Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Iteration JSONL records include a touched_packages field when packages are available\nExisting log decoding remains backward compatible with older records missing the field via zero-value omission\nExisting logging tests updated to assert touched_packages is written when packages are present","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-19T21:51:59Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3bb0ebe55a5031c32235870f9cd1c817e545e9cf0848d7647b8de223813c3bb4","created_at":"2026-02-19T21:37:23Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Extend internal/logger/logger.go IterationLog struct with a touched_packages field and populate it in internal/runner/logging.go when writing iteration log entries. Update existing logger and runner logging tests to assert the new field is written correctly.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-7duv2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Persist touched packages in iteration logs","updated_at":"2026-02-19T21:51:59Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T21:23:19Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7e032c363bd4b65494e6b947566bd4894cb9347e6d71c2098f4c20e174cd6d84","created_at":"2026-02-07T19:01:31Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"In internal/pipeline/status.go:28-33, all other slice fields (UnrefinedIdeas, UnplannedSpecs, UndecomposedPlans) are initialized to []string{}, but ReadyBeads is left nil. If bead.NewClient() fails, ReadyBeads stays nil while others are empty. This causes null vs [] differences in JSON serialization.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-7e5o","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Initialize ReadyBeads to empty slice in PipelineStatus constructor","updated_at":"2026-02-07T21:23:19Z","waiters":"","wisp_type":"","work_type":""} @@ -652,6 +658,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T13:56:03Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7e73f4a2c00ab4be8e387e2cc7c9c84a21250d4711b9c8b02863f56ef52a70e6","created_at":"2026-02-23T13:50:11Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Build.Run records OriginalTier/ActualTier for single-pass flows, but TDD fresh-context path returns phase metrics without explicit top-level tier assertions. Define expected iteration-log semantics for TDD and add tests ensuring tier provenance is consistent across build strategies.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-8w81a","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add tier-metrics parity coverage for TDD fresh-context builds","updated_at":"2026-02-23T13:56:03Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T12:22:44Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"8c14049a8fed42e1de955c59e65af009d4cd1fd5648122108a0da5677328115e","created_at":"2026-02-06T12:15:20Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-8x2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add review timeout awareness to run deadline","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-13T04:29:36Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4795ae4b36f57df8cfa1649dddab2d9dcc5a822d99e337fa60179a14604a1f27","created_at":"2026-02-12T17:44:32Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add RecentValidationFailures []string to prompt.Context and update normalizeNilFields(). Add validationFailures []string field to Runner struct. Implement extractValidationSummary(failureOutput string) string as a package-level function in runner that extracts key error lines from go test/vet output: lines matching --- FAIL:, FAIL\\t, or go vet diagnostic patterns. Cap each summary at 500 characters.\n\nAcceptance criteria:\n- prompt.Context has RecentValidationFailures []string field initialized to empty slice by normalizeNilFields()\n- extractValidationSummary extracts test failure names and error messages from go test output\n- extractValidationSummary extracts diagnostic lines from go vet output","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-8y6e","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add validation failure types and summary extraction","updated_at":"2026-02-13T04:29:36Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"93426e32e9e2e228c5435366b989e2e52f3009f16a1b322f65e85dffe4fdeff3","created_at":"2026-03-07T20:41:51Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/prompt/assembler.go Assemble() method to integrate phase filtering, scoping, and budget shaping. Change signature to Assemble(phase string, fileCount int) string. Layers are trimmed in priority order to fit budget.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-8yarv","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Wire budget shaping into Assemble workflow","updated_at":"2026-03-07T20:41:51Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"TestReadStatus_Valid writes file and verifies parsed fields\nTestReadStatus_NoFile verifies nil return for missing file\nTestIsProcessAlive tests cover both live and dead PIDs","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T11:13:52Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7d97bfbd73ccdb80721203a5554189a0687b84da57c3e411cb3eeb7c2eba1801","created_at":"2026-02-07T08:38:38Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add TestReadStatus_Valid, TestReadStatus_NoFile, TestIsProcessAlive_Self, and TestIsProcessAlive_Dead to internal/runner/status_test.go. Use t.TempDir() for file I/O tests and real syscalls for PID checks.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-8yl","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add tests for ReadStatus and IsProcessAlive","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T06:03:12Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d97342f244e72205b1c70f6a5e0ebdf5307c28394f649748884964a231517358","created_at":"2026-03-07T05:55:33Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add three new methods to the GitAdapter interface and implement them in ExecGitAdapter. Create LogEntry type with Hash and Message fields. Implement Log to return recent commits in most-recent-first order, Show to return commit diffs, and SquashCommits to collapse commits via git reset --soft. Update existing fakes and mocks to compile. Write integration tests using real temp git repos.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-8ztwd","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Extend GitAdapter with Log, Show, and SquashCommits methods","updated_at":"2026-03-07T06:03:12Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-21T08:23:52Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"79efbf36df1227258cf557c9fc41739f31f7911e181908b4e7c5d4934b80e9b5","created_at":"2026-02-21T02:56:46Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Deming's key insight: 94% of problems belong to the system. When a bead fails, distinguish system-caused failures (ambiguous spec, overbroad decomposition, unclear acceptance criteria, prompt issues) from model-caused failures (model genuinely couldn't handle the task). Add a failure_attribution field to iteration_metrics: 'system' (spec/prompt/decomposition set the model up to fail) vs 'model' (task was well-scoped but model was inadequate) vs 'transient' (infrastructure/timeout). Use failure patterns: repeated failures on same bead across tiers = likely system cause; single failure with success on retry at same tier = likely transient.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-90hqq","is_template":0,"issue_type":"feature","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Attribute failures to system causes vs model capability","updated_at":"2026-02-21T08:23:52Z","waiters":"","wisp_type":"","work_type":""} @@ -707,6 +714,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T16:12:43Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"8c3ae41ada583af130a5eb7eb6de19b10a9afee3db9c5584e2ef6f8f8b64b70b","created_at":"2026-02-20T15:36:30Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"In internal/runner/process.go, set bc.Result.OriginalTier to the heuristic-selected tier in setupBeadContext and set bc.Result.ActualTier after execution. Update internal/runner/logging.go to pass both fields through writeIterationLog. Add assertions in internal/runner/logging_test.go verifying both fields appear in JSONL output.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-9wx1w","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Wire OriginalTier/ActualTier through process and logging","updated_at":"2026-02-20T16:12:43Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"After calling any Render* method, LastDiagnostics() returns non-nil with correct PromptType and non-zero EstimatedTokens\nSectionTokens map contains expected section keys for each prompt type (e.g. RenderBuild includes rules, spec, task_identity; RenderReview includes diff)\nAdding LastDiagnostics to PromptRenderer interface compiles with all existing implementations and mocks","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-19T21:40:14Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"15f2a44e7bbf6b4d4e510549e79e7804baaa9ca466a304522bbac2dafb5b8126","created_at":"2026-02-19T19:42:40Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add lastDiagnostics field, LastDiagnostics() method, and computeBuildDiagnostics helper to Renderer in internal/prompt/prompt.go. Add LastDiagnostics() to PromptRenderer interface in internal/runner/interfaces.go and update all mock implementations to return nil. Update internal/prompt/prompt_test.go.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-9xmm1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Wire diagnostics into Renderer and PromptRenderer interface","updated_at":"2026-02-19T21:40:14Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T12:38:18Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"ef58a58661623f77079bbff7f53185c2b4eeb98d2c3a50ed9837433f38e73d06","created_at":"2026-02-06T12:37:21Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-9xp","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update README commands reference table: add board, plan, queue, triage, review commands with their flags","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"9eb36588cb82e813c4d4fd069eeea2412967ebe9b152e5a06f31e8a90cce9479","created_at":"2026-03-07T20:41:51Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/prompt/assembler.go: add ShapeBudget() step that applies scope-adjusted scaling (50% for 1-2 file beads, 75% for 3-4 files, 100% for 5+) to layers and trims them to fit. Produce ShapeReport documenting what was trimmed.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-9z0j1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Implement budget shaping with scope adjustment in PromptAssembler","updated_at":"2026-03-07T20:41:51Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:29Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"0441189deffd5ddcc97e4a88aa6dba7ebe1d00011e2919a813aa709cb3fcd2f4","created_at":"2026-02-18T15:43:39Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Running go test -tags e2e ./test/e2e fails in TestMain when gromit init runs in scaffold dir with error 'find project root: file does not exist'. Investigate why gromit init can't find project root in temp scaffold and fix or adjust test setup.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-9z6m","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Investigate e2e TestMain gromit init failure (find project root)","updated_at":"2026-03-02T19:42:29Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"TestStatusWriter_Write verifies pid field exists in output\nTestStatusWriter_Write_IncludesPID validates PID matches os.Getpid()\nTests use real file I/O with t.TempDir()","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T11:07:08Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b83702b372b03ac362b8ecd49ecd9ee4483cd3b8b4d2f18969d6f523eb14c355","created_at":"2026-02-07T08:38:38Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Update TestStatusWriter_Write in internal/runner/status_test.go to verify the pid field. Add dedicated TestStatusWriter_Write_IncludesPID test that writes status and validates the PID value in the JSON output.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-a1s","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add tests for PID field in status writer","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T07:53:33Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5bc2029fd166312ed884820543c624db288683b01661ba10533d0e8c066b537a","created_at":"2026-02-06T08:12:08Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Improve retro command: enrich stuck bead data with status/comments, replace y/n interactive mode with Claude Code session launch.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-a2j","is_template":0,"issue_type":"epic","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Improve ralph retro: bead context + Claude Code review","updated_at":"2026-02-08T07:53:33Z","waiters":"","wisp_type":"","work_type":""} @@ -740,6 +748,7 @@ {"acceptance_criteria":"go build ./... passes\ngo test ./... passes with zero test failures\nt.Skip count reduced by ~100 from baseline","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:25Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c71842f7ec4467d4e20d511b5962a1f300bdb00590a2d70aa8e3797bda8e0882","created_at":"2026-02-13T11:34:16Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Run full test suite to confirm all changes are clean. Verify go build ./... and go test ./... both pass. Compare t.Skip count before and after (expect ~100 reduction from baseline of ~168). Close overlapping beads gromit-uugq and gromit-08s1 as superseded.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-aqye","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Final verification and bead cleanup","updated_at":"2026-03-02T19:42:25Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-05T17:29:32Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"ed118ec44a729df9fd39c0d8972b2092dda9a7f29c631575278a6a998f992355","created_at":"2026-02-05T17:27:23Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Rename the triage command to refine for better clarity.\n\n## Changes needed\n- Rename cmd/ralph/triage.go → cmd/ralph/refine.go\n- Update command name from 'triage' to 'refine' in cobra definition\n- Update any references in main.go\n- Update help text and README\n\nSimple rename, no logic changes.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-asd","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Rename ralph triage to ralph refine","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Label inheritance logic in decompose pipeline that copies build_strategy:X label from parent to sub-beads\nTests verifying build_strategy:tdd propagates to all sub-beads\nTests verifying absence of build_strategy label is also propagated (no label added)","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-22T22:44:50Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c6d4bb3e95d6d150a9694f757bf16f36393338fdcda2498b90d8ae0d270cc56b","created_at":"2026-02-22T17:18:06Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"In the bead decomposition pipeline (internal/pipeline/decompose/ or equivalent), copy the parent bead's build_strategy:X label onto each generated sub-bead. If the parent has no build_strategy label, sub-beads are created without one and inherit the global config.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-atgpc","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Inherit build_strategy label when decomposing beads into sub-beads","updated_at":"2026-02-22T22:44:50Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"eb7b2b54673a00ba16f67893b2f7d09b5ee2015c3dac6631cdec2e7265aefdf4","created_at":"2026-03-07T20:41:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/loop/spec_loop.go to apply the same Router.Select() logic for spec-level stages (plan, decompose, accept). Populate StageRequest.Model and StageRequest.Provider before calling each stage.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-atr10","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Wire Router into spec loop for plan/decompose/accept phases","updated_at":"2026-03-07T20:41:50Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-03T06:08:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"07e5bc34c7c88ca344bd5b4ce2bdc29128f51bf2b8688a42678daf59d704ce61","created_at":"2026-03-03T04:28:10Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Insert the enricher call in Gate.Run() in internal/pipeline/prepare/gate.go after precheck and before readiness check. Only call when enricher is non-nil and bead has empty ExpectedOutputs. On error log a warning and continue (fail-open). Add gate_test.go tests for the new call site behavior.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-au1yu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Wire CriteriaEnricher call into Gate.Run","updated_at":"2026-03-03T06:08:02Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"TemplateContext has Efficiency field of type pointer to logger.EfficiencyReport\nTemplateContext has Experiment field of type pointer to retro.Experiment","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T01:18:23Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"1a17a23d2bd486849dcc21a2e72f80f52f8a3044631e66db7e0555fcf6945b5e","created_at":"2026-02-07T23:32:16Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/retro/retro.go to add Efficiency and Experiment fields to TemplateContext struct. These fields will hold efficiency report data and active experiment data for template rendering.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-avbc","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Extend TemplateContext for efficiency and experiments","updated_at":"2026-02-08T01:18:23Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"cmd/gromit/plan.go is under 120 lines\ngromit plan behaves identically for both input modes (spec picker, spec name flag)\nPlan existence check and force override work correctly via pipeline package","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-14T07:00:29Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a36e902d4af3c079dff9b2c6db98ef0ded459c5c0e291f43c54d94842de58e5f","created_at":"2026-02-12T13:55:07Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Extract business logic from cmd/gromit/plan.go (currently 265 lines) into the Pipeline.Plan() method implemented in the prior bead. The CLI file should become a thin adapter that handles only: flag parsing, interactive spec picker, calling Pipeline.Plan(), and post-plan chaining to decompose. All spec validation, plan existence checking, bead context gathering, prompt building, agent resolution, and post-processing should live in the pipeline package.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-awh9","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Refactor cmd/gromit/plan.go to thin adapter over Pipeline.Plan()","updated_at":"2026-02-14T07:00:29Z","waiters":"","wisp_type":"","work_type":""} @@ -821,7 +830,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:25Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"828c3299c1fdd48af89d20db3e0cae8b611159de5d2e6339b65ddd640f534fd1","created_at":"2026-02-22T22:17:28Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Currently only the green phase uses before/after usage snapshots via snapshotIterationUsage(). Red and refactor phases use recordPhaseMetric() without snapshots, mixing deltas with raw values. Switch all phases to consistent snapshot-based delta tracking with tests.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-c98eq","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add snapshot-based delta tracking to red and refactor phase metrics","updated_at":"2026-03-02T19:42:25Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-03T06:11:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"865f303c4705f229822741aa9239fe0cddea7ba705acc323539aee76c3ed2aa0","created_at":"2026-03-03T04:28:10Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add UpdateExpectedOutputs(ctx context.Context, id string, criteria []string) error method to *Client in internal/bead/bead.go. Join criteria with newlines and call bd update \u003cid\u003e --acceptance \u003cjoined\u003e. Follow the UpdatePriority pattern. Add tests in internal/bead/bead_test.go.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-c9ozb","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add UpdateExpectedOutputs to bead client","updated_at":"2026-03-03T06:11:02Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Splitting into separate tmux and status file beads for bite-sized work","closed_at":"2026-02-06T07:09:08Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5776751d2d672a0ad4a89b5af72f4490f12291a554a9133ec061681e7e8ad7f0","created_at":"2026-02-06T07:07:33Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Set tmux pane title during runs + write .ralph/status.json for external consumers.\n\nTmux pane title (auto-detected): Ralph: iter 3 - gromit-v1t (sonnet)\nStatus file (.ralph/status.json): {running, iteration, bead_id, bead_title, model, started_at, elapsed_s}\n\nImplementation:\n- Detect tmux via TMUX env var\n- Save/restore pane title on start/exit\n- Update title at start of each iteration\n- Write status.json at iteration start, completion, and exit (running: false)\n- New internal/tmux/tmux.go for tmux detection and title management\n- Use defer + signal handling for cleanup\n\nKey files: internal/runner/runner.go, internal/tmux/tmux.go (new)","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-ca2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Tmux integration + status file during runs","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7ee9865e0b54611c9a44a60cdb4faf3b832cfd7b9c695c1db8bf2ae6fc004d84","created_at":"2026-03-07T15:06:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/v2/loop/resume_integration_test.go with integration test. Setup existing spec and beads with some changed files, run SpecLoop on resume, verify plan and decompose are skipped, gap analysis identifies changed beads, and flagged beads are re-queued. Test covers resume flow acceptance criteria.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cbn9c","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Integration test for resume with gap analysis","updated_at":"2026-03-07T15:06:50Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T20:29:34Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7ee9865e0b54611c9a44a60cdb4faf3b832cfd7b9c695c1db8bf2ae6fc004d84","created_at":"2026-03-07T15:06:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/v2/loop/resume_integration_test.go with integration test. Setup existing spec and beads with some changed files, run SpecLoop on resume, verify plan and decompose are skipped, gap analysis identifies changed beads, and flagged beads are re-queued. Test covers resume flow acceptance criteria.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cbn9c","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Integration test for resume with gap analysis","updated_at":"2026-03-07T20:29:34Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Finds current binary using os.Executable() with os.Args[0] fallback\nConnects subprocess stdio to parent process stdio\nReturns nil for *exec.ExitError, propagates other errors","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T22:45:11Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f6c57518d5da7fcd3530d081614518989764e17f753db4bf1effb433b512406b","created_at":"2026-02-06T22:25:41Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create execGromit function in cmd/gromit/chain.go that accepts variadic string args. Uses os.Executable() to find current binary (fallback to os.Args[0]). Creates exec.Command with args, connects stdio to os.Stdin/Stdout/Stderr, runs subprocess. Treats *exec.ExitError as nil (subprocess printed own errors).","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cbu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement execGromit subprocess executor","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T06:09:27Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7ed442c66c0ef64abb67b86c9f5ee9b124fbe572ab7391d8f1436696d1d19579","created_at":"2026-03-07T05:55:33Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create SquashPerBead function in internal/v2/pipeline/squash.go that reads git log, groups per-stage commits by bead ID, and squashes them into single per-bead commits for PR presentation. Define SquashGit interface with Log and SquashCommits methods. Parse structured commit messages with ParseCommitMessage, identify bead boundaries, assemble squash messages from bead titles, and call SquashCommits with the correct base hash. Handle no-op case when no structured commits exist.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cbvlu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement per-bead squash logic","updated_at":"2026-03-07T06:09:27Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Gets diff, renders refactor prompt, calls Claude Run with build model, and re-validates\nOn validation failure reverts to pre-refactor commit via git reset --hard and retries once\nOn second validation failure skips refactoring with warning and returns nil","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T09:03:30Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4088a73ff30119c3c686ad774f3e73ea58ed1db2f1dfc3b76eb1cb278d79f3a4","created_at":"2026-02-07T05:48:27Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add runRefactorPhase method to Runner in internal/runner/process.go that captures pre-refactor commit, renders refactor prompt, calls Run, re-validates, and handles validation failure by reverting and retrying once. Skips refactoring on second failure.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cc9","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement runRefactorPhase method","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} @@ -857,6 +866,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-21T09:44:11Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"74dbaaa861e7253b1fbd62edecd591055e19b524a9cc07ff37b21c1c58331509","created_at":"2026-02-20T10:47:37Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"In processCodexStream, turn.completed events overwrite the usage variable while response.completed events correctly merge via mergeCodexUsage. In multi-turn sessions, earlier turn usage data is discarded. Apply mergeCodexUsage consistently.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-ct8wi","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Use mergeCodexUsage for turn.completed events in codex_stream.go","updated_at":"2026-02-21T09:44:11Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"No test in codex_test.go references result.Stdout\nTestCodexProviderRunCapturesStderr asserts result.Output is empty and result.Stderr contains the error message\nAll tests pass: go test ./internal/provider/...","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-18T16:52:42Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"efdbddacd0e24331b0001281fd67bbf81aefd218ebaa2a429d56729b668a7d37","created_at":"2026-02-18T16:25:43Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Update five test sites in `internal/provider/codex_test.go` that reference `result.Stdout` to use `result.Output` instead. Update `TestCodexProviderRunCapturesStderr` to assert `result.Output` is empty (not containing stderr content) and `result.Stderr` contains the error. Run `go test ./internal/provider/...` to confirm all tests pass.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-ctgu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update codex_test.go assertions to match new Output semantics","updated_at":"2026-02-18T16:52:42Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Test verifies default threshold is 60\nTest verifies custom threshold loads from YAML\nTest verifies zero threshold gets default of 60","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T23:59:11Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"45c9abef81b212b6265bf82e25068d0754ba3d43b55ae04eb5c322e63c0d612a","created_at":"2026-02-07T22:39:04Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add test cases to internal/config/config_test.go verifying default threshold behavior, custom threshold loading from YAML, and zero threshold defaulting to 60.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-ctpu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add StateConfig tests","updated_at":"2026-02-07T23:59:11Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:04:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c16a479ed41a37649220762188143ad2f33692636fa4080f6bba211a59a30c13","created_at":"2026-03-07T20:41:52Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add tests to internal/v2/loop/spec_loop_test.go: existing plan + beads -\u003e gap analysis -\u003e selective revalidation -\u003e correct bead list, remediation receives correct worktree, Request template populated.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cufx0","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add integration tests for resume with gap analysis and revalidation","updated_at":"2026-03-07T21:04:07Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:38Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3267dbf98a1d3429a7c46cd340d36b8269c82b562fbb4336883eca397afd551b","created_at":"2026-02-22T22:17:05Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add unit and integration tests verifying the --choose-agent flag is parsed correctly and affects agent selection in the refine command.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cvl1y","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add unit tests for --choose-agent flag in refine command","updated_at":"2026-03-02T19:42:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"showStatus function body replaced with call to runner.PrintStatus\nOld manual status.json reading and field-by-field printing removed\nAcceptance tests pass: go test -tags acceptance ./internal/runner/acceptance/ -run Status","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-22T17:50:08Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"2f839a21fa13a07d82fe2c50defc98bfaf3bf30ba683fc852be7bcf9d3b0f3d4","created_at":"2026-02-22T12:04:31Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Replace the showStatus function body in cmd/gromit/main.go:220-266 with a call to runner.PrintStatus(gromitDir, cfg, os.Stdout, nil). Remove the old manual status.json reading and field-by-field printing. Verify acceptance tests pass: go test -tags acceptance ./internal/runner/acceptance/ -run Status. See .gromit/reports/debug-20260222-120033.md","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cwk4z","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update gromit status CLI to call runner.PrintStatus","updated_at":"2026-02-22T17:50:08Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-03-05T15:37:30Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"21481f167ba53464199d40646aa36b958393f5b41f66f0f050c1433b2e61e0f8","created_at":"2026-03-05T15:28:05Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"internal/events/types_test.go:139 checks len(eventTypes) \\!= 36 but the error message says 'Expected 35'. Also, GateReadinessBlockEvent (defined in types_gate.go:49) is missing from both this test and the specEventCases helper, so new event types can be added without uniqueness or timestamp verification.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-cx22d","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix event type count mismatch in types_test.go","updated_at":"2026-03-05T15:37:30Z","waiters":"","wisp_type":"","work_type":""} @@ -868,6 +878,8 @@ {"acceptance_criteria":"Replace interface{} with typed signatures in pipeline interfaces","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-21T07:22:57Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7015c7ded182cfcee5f6339b71a35b5b5c3f92c21b66feafd4167cad7f2442ae","created_at":"2026-02-13T05:35:45Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"internal/pipeline/pipeline.go defines ClaudeClient.Run() returning interface{}, BeadClient methods returning interface{}, PromptRenderer methods accepting interface{}, and LogWriter.Write accepting interface{}. This leads to unsafe type assertions at runtime (e.g., pipeline.go:213 asserts map[string]interface{} from ClaudeClient.Run result). The type assertion at line 213 will fail at runtime if the ClaudeClient implementation returns anything other than map[string]interface{} with exact key names 'Success', 'Output', 'ExitCode'.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-d0n0","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Replace interface{} with typed signatures in pipeline interfaces","updated_at":"2026-02-21T07:22:57Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- [ ] New section added to PROMPT_build.md between \"Instructions\" and implementation steps\n- [ ] Instructs Claude: \"If this task requires changes across more than 3 unrelated areas, or would need more than ~200 lines of new code across multiple packages, output SCOPE_TOO_LARGE: followed by a brief explanation and a suggested breakdown into 2-4 sub-tasks, then exit\"\n- [ ] Instruction is clear that Claude should STOP and not attempt partial work\n- [ ] Suggested breakdown format specified (numbered list of sub-task titles)\n- [ ] Template still renders correctly with existing prompt.Renderer","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-05T21:00:17Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"974f6d80f3eac198576ed2c232890b5745d5b8fa7d57167ce967ad4fe3c8b37c","created_at":"2026-02-05T19:44:24Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Update PROMPT_build.md to instruct Claude to self-assess task scope before starting work. If the task is too large for a single iteration, Claude should output a special marker instead of attempting it.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-d25","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add scope-awareness instruction to build prompt template","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"invocation_timeout_acceptance_test.go uses config.ModelsConfig (not config.ModelConfig)\nworktree_merge_acceptance_test.go unused config import removed\nAll acceptance tests compile under go test -tags acceptance -run '^$' ./...","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-22T16:49:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c0559bf490da83bc2aa2fa3df59786b1d27af989477056ea95ded48279e2d087","created_at":"2026-02-22T12:01:21Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Three acceptance test compilation fixes found during runner cleanup review: (1) invocation_timeout_acceptance_test.go:34 uses config.ModelConfig but type is config.ModelsConfig, (2) worktree_merge_acceptance_test.go:13 has unused config import, (3) verify all acceptance tests compile under -tags acceptance. The larger PrintStatus and format.go restoration work is tracked in gromit-jetjq and gromit-cr50o.1/2/3 respectively.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-d2aj2","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix acceptance test compilation errors (ModelsConfig typo, unused import, build tag verification)","updated_at":"2026-02-22T16:49:02Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d6f36344680e7f7be85b153dcfa5329294f5c67bd12e037404f6980d1a706f7e","created_at":"2026-03-07T20:41:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify cmd/gromit/run2.go to create a Router instance from cfg.Providers and cfg.Routing. Register LLMProvider implementations (Claude, Codex, others) by provider name. Pass the router to SpecLoop and BeadLoop constructors.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-d2rpx","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Instantiate and wire Router in run2 command","updated_at":"2026-03-07T20:41:50Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"66b517bf8ed4941bfedf58a4b92426ed11d6fe1c72f0a10eb6dc3016889d9d3f","created_at":"2026-03-07T20:41:52Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add integration tests to internal/v2/loop/bead_loop_test.go: mock router + stages, verify provider/model in StageRequest per phase, verify tier bumps on retry, verify router re-called with higher tier.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-d4134","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Add integration tests for bead loop routing and escalation","updated_at":"2026-03-07T20:41:52Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Comma-separated non-zero counts in lifecycle order\n(X this run) only when HasRunInfo=true AND ClosedThisRunCount \u003e 0\nAll-zero case displays Beads: none\nReady bead IDs still appear below","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-11T12:13:36Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"55b1bce340eef4a8c49e794188ccf306c431d53bfb2e4142e7f0b6e616fa26b8","created_at":"2026-02-11T07:42:16Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Update formatPipeline in internal/runner/format.go to build comma-separated list of non-zero bead counts in lifecycle order (ready, in-progress, blocked, deferred, closed). Append (X this run) to closed count when HasRunInfo is true and ClosedThisRunCount is greater than zero. Preserve ready bead ID list display below the counts line.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-d46r","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Rewrite formatPipeline for status breakdown","updated_at":"2026-02-11T12:13:36Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Decomposed into smaller beads per RULES.md scope constraints (3+ files, cross-cutting refactor)","closed_at":"2026-02-10T16:51:03Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"85011c4f28f01d3c78f7bcb616edd1f64b4f090540b59ae3291c265bb93ffcfd","created_at":"2026-02-08T07:36:07Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"The claudeRunnerAdapter struct is copy-pasted in 3 locations: cmd/gromit/review.go, internal/runner/runner.go, and internal/retro/retro.go. All three convert claude.Result to learnings.Result but differ slightly (review.go lacks nil-result check). Extract into a single definition in internal/learnings/filter.go or a new internal/claude/adapter.go, then update all three call sites to use the shared type. Also consolidate the hardcoded projectName/projectDesc strings (which vary between call sites: 'runs the Gromit loop correctly' vs 'runs the Gromit loop with fresh context on each iteration') into a single constant or config field.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-d6sl","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Consolidate triplicated claudeRunnerAdapter into shared package","updated_at":"2026-02-10T16:51:03Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"SKILL.md contains complete orchestrator instructions with dashboard and all dispatch modes\nPlaceholder markers present for REFINE_SKILL_CONTENT, PLAN_SKILL_CONTENT, and DECOMPOSE_SKILL_CONTENT\nDashboard instructions read backlog.jsonl, scan specs/plans, and run bd ready","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T05:32:30Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c1478c99e035da7bab963aecc6342b00288562fc053b473bc88be92cf2b9a975","created_at":"2026-02-07T04:50:46Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create skills/gromit-orchestrator/SKILL.md with frontmatter, dashboard instructions, and stage dispatch instructions. Include placeholder markers for refine/plan/decompose skill content inlining.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-d6y","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create orchestrator skill template","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} @@ -1009,6 +1021,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-04T09:13:20Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f66f5b2585e35df2a1fbe2848b2768765eae420f910918010a1b89de261df4bd","created_at":"2026-03-03T21:48:25Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add StartCommit string field to pipeline.Input and TrivialAutoFixed bool field to pipeline.Output in internal/pipeline/stage.go. Both fields need doc comments explaining their purpose.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-euxuu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add StartCommit and TrivialAutoFixed to pipeline types","updated_at":"2026-03-04T09:13:20Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"interactive-state.json persists pending_worktree_branches array and loads cleanly when absent\nAdd/remove helpers are deduplicated and thread-safe under file-locking\nExisting state tests pass with backward compatibility preserved","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T19:03:35Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d66d41bf1ad13ae89cb99140f6f018fbd832d8e4a02256a0dedc21cec761bcb0","created_at":"2026-02-15T20:17:36Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add pending_worktree_branches field and helper methods to internal/state/interactive_state.go for add/remove/list operations with file-locking safety. Update internal/state/interactive_state_test.go to verify JSON persistence, backward compatibility, and deduplication.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-evne","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add interactive state pending branch tracking","updated_at":"2026-02-20T19:03:35Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"truncate handles maxLen \u003c 3 without panicking\nMulti-byte UTF-8 characters are not split mid-sequence\nTests cover edge cases for small maxLen and non-ASCII input","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T20:32:55Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c6ea67841d986b3c9a8038a5941eef7be27046c7820a89598bea9f38225a6d46","created_at":"2026-02-07T06:02:53Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"In internal/pipeline/status.go line 169, truncate will panic with a negative slice index if called with maxLen \u003c 3. Currently only called with 50, but should be guarded. Also operates on bytes not runes, which will corrupt multi-byte UTF-8 characters in user-supplied backlog text. Fix both issues.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-evq","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add truncate guard for small maxLen and UTF-8 safety","updated_at":"2026-02-07T20:32:55Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"29fedf77ab50b421d564edcbca08ce2fcd2413897a6b8e0c800f8527d76e645f","created_at":"2026-03-07T20:41:49Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Implement the Router.Select() method with 3-layer selection: phase preferences from routing.phase_preferences config, ratio balancing across providers by invocation count for 'any' phase work, and fallback with cooldown on usage-limit errors. MarkUnavailable and RecordInvocation manage provider state.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-ew1l8","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Implement Router ratio balancing and phase preferences logic","updated_at":"2026-03-07T20:41:49Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Wire circuit-breaker into Router and runner invocation path","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:38Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e7a213f782c5eddeb3cf808c4651367b6f3a28f1c01b3a82bebe1722fa96b8cd","created_at":"2026-02-18T20:49:26Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add CircuitBreaker field to Router. In selectByRatio(), call cb.EffectiveRatio() to get adjusted ratios when circuit-breaker is enabled. After each invocation in the runner, call router.RecordOutcome(providerName, failureCategory). Initialize circuit-breaker from config in buildRouterAndLearningsProvider(). When circuit_breaker config is absent or disabled, Router behaves as today. Spec: routing-ratio-reliability-review Part 3.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-ewhf4","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Wire circuit-breaker into Router and runner invocation path","updated_at":"2026-03-02T19:42:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Test sets content to literal hello\\nworld using raw string\nVerifies file contains hello\\nworld\\n without newline interpretation\nExisting TestFakes_ClaudeWriteFile regression test still passes","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T08:12:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"65ed2e9f1b9652598da658cbac7bf57caeb31856079b9d0e16959f86a9a09776","created_at":"2026-02-07T05:43:45Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add TestFakes_ClaudeWriteFile_BackslashContent to test/contracts/fakes_integration_test.go that verifies content with literal backslash-n sequences is written without escape interpretation. Use Go raw string literal for test input.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-ewl","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add backslash content fidelity test","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"1. Run() accepts a deadline time.Time parameter and stops starting new beads after the deadline passes\n2. Zero-value deadline means no time limit (backward compatible)\n3. Deadline check is positioned after the iteration limit check and before fetching the next bead","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T11:24:10Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a26e55daf917d248b1731e52316130729f0882609bc85a94e54203e872ba7a15","created_at":"2026-02-06T10:01:00Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add a deadline parameter to Runner.Run() and check it at the top of the loop. Change the Run() signature from Run(ctx context.Context, maxIterations int, dryRun bool) to Run(ctx context.Context, maxIterations int, deadline time.Time, dryRun bool). Add a deadline check right after the existing maxIterations check (line 174) in internal/runner/runner.go: if !deadline.IsZero() \u0026\u0026 time.Now().After(deadline) { r.log(\"Time budget expired, stopping\"); break }. The check position means the current bead always finishes before the loop exits.","design":"See docs/plans/2026-02-06-time-budget-design.md. The deadline check goes on line 178 (after the maxIterations break on line 176). Log message: 'Time budget expired, stopping'. This is the only change to runner.go.","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-eww","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add deadline check to runner loop","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} @@ -1023,6 +1036,7 @@ {"acceptance_criteria":"When `RunFn` is set, `(*Client).run()` uses it and does not enter subprocess execution.\nWhen `RunFn` is nil, `(*Client).run()` still invokes the subprocess flow with the same command/binary and `Dir` behavior as prior code.\nNo conditional logic in `run()` references any removed or legacy hook field.","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T04:40:16Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"cd9d8508519e1b6c640099206af78d220f9545c9b5905789718621881df7aa21","created_at":"2026-02-20T04:34:27Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Refactor `(*Client).run()` so it checks only `RunFn`; when `RunFn` is nil it must execute the subprocess path exactly as before.\n\nAcceptance criteria:\n- When `RunFn` is set, `(*Client).run()` uses it and does not enter subprocess execution.\n- When `RunFn` is nil, `(*Client).run()` still invokes the subprocess flow with the same command/binary and `Dir` behavior as prior code.\n- No conditional logic in `run()` references any removed or legacy hook field.\n","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-f2ng8.2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Make Client.run consult only RunFn before subprocess","updated_at":"2026-02-20T04:40:16Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"A subprocess failure (`*exec.ExitError`) still returns an error that includes wrapped stderr context exactly as before.\nNon-exit errors and successful subprocess runs retain prior return behavior.\nRegression tests covering fallback subprocess semantics pass.","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T04:43:34Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7251ff57557dfedd59c0f68bffd6a00ccd9fe09322ce3ce5eacb4b94455dc496","created_at":"2026-02-20T04:34:28Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add/update tests around `(*Client).run()` fallback execution to verify stderr augmentation on non-zero exits remains unchanged after the hook consolidation.\n\nAcceptance criteria:\n- A subprocess failure (`*exec.ExitError`) still returns an error that includes wrapped stderr context exactly as before.\n- Non-exit errors and successful subprocess runs retain prior return behavior.\n- Regression tests covering fallback subprocess semantics pass.\n","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-f2ng8.3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Lock in subprocess error wrapping behavior","updated_at":"2026-02-20T04:43:34Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Stuck after 3 failures. Closing to recreate with explicit before/after code diff.","closed_at":"2026-02-19T16:47:31Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b966a86b8df475ed3d0ca2faf5db2b261b9022f2a42c3749feb9a482fd0b736a","created_at":"2026-02-19T14:10:59Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"cmd/gromit/refine.go:156 -- createRefinePipeline returns nil on error but the caller at line 61 dereferences without nil check, causing a runtime panic if backlog.NewFile fails. Change signature to return (*pipeline.Pipeline, error) and propagate the error, matching the pattern used by other commands.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-f37u5","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix nil pointer dereference in createRefinePipeline","updated_at":"2026-02-19T16:47:31Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:09:44Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5bb297a354df2fbeb93572c6aa6e641e3ef1bc931eb6cea6e0d0ef87ff94491b","created_at":"2026-03-07T20:41:51Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/prompt/assembler.go: implement loadProjectContext(bead BeadInfo) that scopes CLAUDE.md to packages touched by current bead. Use v1's applyScopedClaudeContext() logic for package filtering.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-f4ouz","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add scoped CLAUDE.md loading to PromptAssembler","updated_at":"2026-03-07T21:09:44Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Add compile-time interface checks for adapter types","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:31Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"807de4ce03c5da2e1f9048b9f219a20aeadc86d9e443904658f9daf642e09bc9","created_at":"2026-02-18T11:16:30Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Multiple adapter types in cmd/gromit/ (claudeClientAdapter, beadClientAdapter, cliAgentResolver, cliPromptRenderer, etc.) and internal/runner/adapters.go (routerAdapter, successLearningRouterAdapter, etc.) implement interfaces but have no compile-time checks. Also callbacks.go lines 411-412 use 'var _ InterfaceType = nil' instead of '= (*ConcreteType)(nil)'. Per project rule: 'Interface files must include compile-time checks.'","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-f4q0","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"Experiment: demoted P1→P2 (haiku). Mechanical: add var _ Interface checks. Tracking tier-demotion-2026-02-20.","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":4,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add compile-time interface checks for adapter types","updated_at":"2026-03-02T19:42:31Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"grep -ri ralph internal/runner/format_test.go returns no matches\ngo test ./internal/runner/... passes","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T18:51:17Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b1e513ebc8f6a28745ae09def0434ed0f63e8bbd0e7c918f628624d6bf03242a","created_at":"2026-02-07T18:27:23Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Replace hardcoded test fixture bead IDs in internal/runner/format_test.go with gromit- prefixed equivalents. Update ralph-runner-abc123 to gromit-abc123 and gromit-2pm to gromit-ja5m, and update corresponding expected output strings.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-f4t","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update format_test.go test fixtures","updated_at":"2026-02-07T18:51:17Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"ATDD fallback triggers for startup errors (not just transport disconnect)\nFallback decision logs include primary provider, fallback provider, and failure class\nProvider marked unavailable with cooldown for both transport and startup failures","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-21T01:51:40Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"20af15a40fd9cf8b544f88485bcf290e5b24b3d28dd355f545254e0ff428c979","created_at":"2026-02-15T19:56:44Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Extend codex-transport-fallback in internal/runner/callbacks.go to trigger on startup errors. Add FailureCategoryStartupError constant to internal/provider/codex.go. Update classifyCodexFailure to detect startup patterns. Create isATDDFallbackEligible helper checking transport_disconnect OR startup_error. Add structured log lines with primary/fallback provider/model and failure class. Add tests to internal/runner/callbacks_test.go.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-f6bo","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Extend ATDD fallback for startup errors and structured logging","updated_at":"2026-02-21T01:51:40Z","waiters":"","wisp_type":"","work_type":""} @@ -1049,6 +1063,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-18T15:56:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7ac12aa4ca462e90c8beda5a69f5ee91471828cea0454dd0e9c5b3f3ee1a24dd","created_at":"2026-02-18T11:16:34Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"cmd/gromit/epic_test.go has ~14 instances of t.Skip('External dependencies not available...') or t.Skip('bd binary not available...'). These tests should either be behind //go:build acceptance tag, have the dependency mockable, or be deleted if they cannot run in CI.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-fgqy","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Clean up epic_test.go permanently-skipped tests","updated_at":"2026-02-18T15:56:51Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Test proves refactor timeout followed by successful validation in separate context\nTest proves validation timeout classification independent of refactor timeout\nTest proves default fallback behavior when phase timeout config is omitted","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-18T14:07:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d3351de6652caa7e4971acf50228820372b46ca221ce8024a163566b5bb4247a","created_at":"2026-02-16T18:12:01Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Expand internal/runner/process_methodology_context_test.go with integration-style tests proving refactor timeout followed by successful validation, validation timeout classification independent of refactor timeout, and default fallback behavior when phase timeout config is omitted. Expand methodology/methodology_test.go and config/config_test.go with matching scenarios using fake callbacks and short in-memory timing controls.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-fgty","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add end-to-end timeout isolation regression tests","updated_at":"2026-02-18T14:07:07Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T13:33:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b204c3e1ddfe6fbaad74b2139e38f0398bd474ead5512bbdba206c73c6e707a5","created_at":"2026-03-07T11:41:44Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add StageCommitter interface and field to BeadLoop, wire CommitStage calls after Build, Validate, and Review stages, and remove the legacy commitBeadWork method. Update bead_loop.go and bead_loop_test.go with unit tests using a recording fake to verify correct calls.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-fh4cu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add StageCommitter to BeadLoop and replace legacy commitBeadWork","updated_at":"2026-03-07T13:33:51Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:17:47Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c1fc543f7a11108460e7e227a130bb6f3bd1baa408cb469eb355f2e4ecc0b928","created_at":"2026-03-07T20:41:51Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/loop/bead_loop.go: when gate returns DecisionBlock, re-queue the bead to end of current pass instead of failing. After a full pass with zero progress (all remaining beads blocked), stop with descriptive error.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-fhp2q","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement gate re-queue logic in bead loop","updated_at":"2026-03-07T21:17:47Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Completed","closed_at":"2026-03-06T02:02:35Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7e6482f9f5531b2cd4ddb3517a19f8974520015a74a08cc32b4f37e43a1c7f24","created_at":"2026-03-05T20:31:24Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Extend internal/v2/loop/bead_loop.go to track and enforce a generation cap. Record start generation (highest gen:N at loop entry). When any bead generation reaches start_generation + cap (configurable, default 3), prevent Review and Decompose from enqueueing new beads. Emit GenerationCapReached event. Return CapHit signal in BeadLoopResult. Update bead_loop_test.go.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-fj1ad","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add generation cap enforcement to bead loop","updated_at":"2026-03-06T02:02:35Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Repetitive scenario groups converted to table-driven tests\nTotal line count decreases while preserving scenario coverage\nTests remain readable and deterministic","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-16T02:04:35Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"febc3d95d78bc6c54180b46cefd1ae9e1a63c37b07249c32e16ab287dff49094","created_at":"2026-02-16T01:08:18Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Consolidate repetitive setup/assert patterns in cmd/gromit/review_spec_validation_acceptance_test.go into shared setup helpers and table-driven tests. Remove duplicated fixtures where test structure only differs by input/expected outcome.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-fj9i","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Table-drive cmd acceptance scenarios","updated_at":"2026-02-16T02:04:35Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T14:06:57Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"013f941497c592433afeafbb768f1ae8cb6af720d23242730a51b4e682c6b06b","created_at":"2026-02-23T13:50:10Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Build currently has local nextTierFromChain fallback plus Config.NextEscalationTier, which duplicates escalation policy and risks divergence. Refactor to one shared escalation path, keep legacy tier/model compatibility via config accessors, and add tests for disabled escalation, tier chains, and legacy-model chain entries.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-fjxy1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Unify build escalation to a single tier-advance path","updated_at":"2026-02-23T14:06:57Z","waiters":"","wisp_type":"","work_type":""} @@ -1264,6 +1279,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Replacing — need to split into CLI launcher + skill (two separate beads)","closed_at":"2026-02-06T07:43:36Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"02517c24e249c0dc1154a5054509cb3754f651de0a12330b05b1ab1148ad1850","created_at":"2026-02-06T07:41:56Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"A Claude Code skill for walking the backlog.jsonl and brainstorming each idea into beads.\n\nLocation: skills/ralph-refine/SKILL.md (in ralph-runner repo, or as a plugin)\n\nThe skill:\n1. Reads .ralph/backlog.jsonl\n2. For each idea, brainstorms it conversationally with the user (iterative, like brainstorming skill)\n3. Asks questions one at a time to understand the idea\n4. Proposes 2-3 approaches with tradeoffs\n5. Decomposes into bite-sized beads following sizing guidelines\n6. Creates beads via bd create after user approval\n7. Removes processed items from backlog.jsonl\n\nUses opus for the Claude model.\nFollows superpowers skill structure.\nReferences superpowers:brainstorming methodology for the conversational flow.\n\nAcceptance criteria:\n1. SKILL.md exists with proper frontmatter\n2. Walks backlog items interactively\n3. Creates properly-sized beads with acceptance criteria\n\nKey files: skills/ralph-refine/SKILL.md (new), .ralph/backlog.jsonl (reads)","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jsb","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create ralph-refine Claude Code skill","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"UpdateGlobalStats creates file on first call with initial run stats\nUpdateGlobalStats merges correctly on subsequent calls, adding to existing per-model totals\nWrite uses atomic temp-file plus os.Rename pattern to prevent corruption","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-11T14:42:03Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"6d2ec39bfb4254e0adc1fb22b8e91ea5290d8d3f6e8c61064d0496a686662f5e","created_at":"2026-02-11T08:24:54Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add UpdateGlobalStats function to internal/logger/globalstats.go that merges run ModelStats into global totals using read-modify-write with atomic temp-file-then-rename pattern. Handle missing file on first call.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jsta","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement UpdateGlobalStats with atomic writes","updated_at":"2026-02-11T14:42:03Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"runAcceptanceTests uses router.Select() with phase=\"build\" and tier from selectTier\nUsage limit errors trigger MarkUnavailable and retry\nATDD acceptance test execution works correctly through provider interface","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-11T20:29:58Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"12f5cb84d5b41864c6bda89e27e69810500efcb8af5eefe1e5a2c1ea00e9fcca","created_at":"2026-02-11T09:27:33Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Convert runAcceptanceTests in internal/runner/process.go from r.claude.StreamRun() to router-based call with phase=\"build\", tier=selectTier(bead). Implement usage limit detection and fallback retry logic matching executeClaudeInvocation pattern.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-juyb","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Convert runAcceptanceTests to Router","updated_at":"2026-02-11T20:29:58Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:23:30Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"994be0fa6c387e21f57e19be67b816ccd47a5d5d0bc2ef238fa2189c4dd9bfbe","created_at":"2026-03-07T20:41:49Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create the internal/v2/routing/ package and define the Router struct with Select(phase, tier) -\u003e (LLMProvider, model, error), MarkUnavailable(name), and RecordInvocation(name) methods. This is the foundation for multi-provider routing.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jv6ns","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Create Router interface and stub in v2/routing","updated_at":"2026-03-07T21:23:30Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T20:53:26Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b65d94475cc69cbb52b0523d7c297e54a2619e4893b59ff41bac701bcfeb0e20","created_at":"2026-02-07T10:14:22Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"test/e2e/e2e_test.go line 40 uses defer os.RemoveAll(tmpDir), but line 109 calls os.Exit(exitCode) which kills the process without executing deferred functions. The contracts package handles this correctly by calling os.RemoveAll explicitly before os.Exit. Apply the same pattern.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jva","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Fix E2E TestMain temp directory leak","updated_at":"2026-02-07T20:53:26Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Run summary aggregation structures and builder logic\nRun-scoped JSONL filtering by run identifier\nDeterministic serialization/writer for summary artifact output\nUnit tests for determinism, required metric fields, run scoping, and malformed input handling","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:39Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"06fa1308c6b194da59fa1abee084b1a3df10e0bda3891dd17e9f551ceea1ad83","created_at":"2026-02-24T04:25:15Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/logger/run_summary.go and internal/logger/run_summary_test.go to aggregate run-scoped JSONL data into a deterministic summary artifact. Include per-phase tokens, cost, latency, retry/validation outcomes, totals, and mode usage indicators derived from telemetry records.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jw28c","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Build deterministic run summary aggregator","updated_at":"2026-03-02T19:42:39Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-06T10:09:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f6eec9927621b3bf637589ad93d71fcdabe269380e9eb4386db987e2823908f3","created_at":"2026-03-05T20:31:24Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/v2/loop/bead_loop.go with the inner bead loop. Uses DAG resolver to pick beads in dependency order. Runs Gate-\u003eBuild-\u003eValidate-\u003eReview-\u003eEpilogue per bead. Gate Skip/Block and Build/Validate Fail all short-circuit to Epilogue failure path. Emits BeadStarted and BeadCompleted lifecycle events. Loops until no more eligible beads. Add bead_loop_test.go using fake adapters.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jx4fi","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement bead loop core stage sequencing","updated_at":"2026-03-06T10:09:07Z","waiters":"","wisp_type":"","work_type":""} @@ -1274,6 +1290,7 @@ {"acceptance_criteria":"IsMethodologyActive returns true when atdd:true label present regardless of global default\nIsMethodologyActive returns false when atdd:false label present regardless of global default\nIsMethodologyActive returns globalDefault when no matching label exists","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T08:31:31Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"ca35a9aa1d7276225744b549c56860cd1ab28d93a3188d0d8c07feb2854ea1f0","created_at":"2026-02-07T05:48:27Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Implement IsMethodologyActive function in internal/bead/bead.go that checks for methodology:true and methodology:false labels, returning label value if present or globalDefault if not. Follows existing HasLabel pattern.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jyo","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add methodology label helper to bead package","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Pipeline decompose tests that currently fail only due to output_missing mismatch are green\\nValidationStats fields (Attempts, ViolationCount, SucceededAfterRetry, Improved) match retry-path expectations\\nNo bead creation occurs when batch contract violations remain at retry cap","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T09:09:44Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"02db23b1067033fe06664419010739cc55b2fe0b762b757d67a614e52bd14e6d","created_at":"2026-02-23T09:02:01Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Recent decompose validation enforces expected_outputs on all candidates, but several pipeline decompose paths and fixtures still emit bead defs without expected_outputs. This causes persistent output_missing violations, extra retries, and incorrect ValidationStats in retry-related tests. Align prompt contract, fixture payloads, parser defaults, and retry accounting so behavior is intentional and stable.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jysme","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Restore expected_outputs contract parity in decompose pipeline retry loop","updated_at":"2026-02-23T09:09:44Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"No remaining references to the old beadContext type\ngo build ./internal/runner/... compiles cleanly\ngo test ./internal/runner/... -count=1 passes with no assertion changes","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-13T17:10:18Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"cffbfbb5b58155f8b65a068a758ee77975137b09b42dec50f890a184e9b4833e","created_at":"2026-02-13T15:52:27Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Replace all uses of the local beadContext struct with runtypes.BeadContext. Delete the beadContext struct definition from process.go:27-52. Update setupBeadContext() to return *runtypes.BeadContext. Promote all field references across process.go and runner.go (~150 sites): bc.bead→bc.Bead, bc.model→bc.Model, bc.tier→bc.Tier, bc.result→bc.Result, bc.parent→bc.Parent, bc.promptCtx→bc.PromptCtx, bc.buildPrompt→bc.BuildPrompt, bc.startCommit→bc.StartCommit, bc.iteration→bc.Iteration, bc.retriesThisModel→bc.RetriesThisModel, bc.totalRetriesThisBead→bc.TotalRetriesThisBead, bc.maxRetries→bc.MaxRetries, bc.maxRetriesPerBead→bc.MaxRetriesPerBead, bc.parentCtx→bc.ParentCtx, bc.beadTimeout→bc.BeadTimeout, bc.runDeadline→bc.RunDeadline, bc.scopeEstimate→bc.ScopeEstimate, bc.buildProvider→bc.BuildProvider. Update all function signatures accepting/returning *beadContext.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jz5w","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Migrate beadContext to runtypes.BeadContext","updated_at":"2026-02-13T17:10:18Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:26:46Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"03c6f96e988adae67e2b6daa4ab8cb6333d0ff8f2735ec6e5b178f9e54ae8279","created_at":"2026-03-07T20:41:49Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create tier.go with tier resolution logic: check methodology.phase_models[phase] for override, fall back to priority/labels via BuildTierForStrategy(), map abstract tier to provider-specific model name. This drives tier-aware model selection.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jzbe3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement tier resolution in internal/v2/routing/tier.go","updated_at":"2026-03-07T21:26:46Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Tier field on DecomposeInput struct\nUpdated Pipeline.Decompose() using input.Tier\nRemoved or unexported decomposeModel constant\n\"medium\" default fallback in Pipeline.Decompose() when Tier is empty\nTest: Decompose passes explicit tier string to the client\nTest: Decompose uses \"medium\" when Tier is empty","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-22T20:30:10Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"0dd20fd01b581716fcbccf116068e91a79edeea887f72471bdf0ed586232df8f","created_at":"2026-02-22T19:25:25Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add Tier string field to DecomposeInput in internal/pipeline/types.go. Update Pipeline.Decompose() in internal/pipeline/decompose.go to pass input.Tier to the Claude client instead of the hardcoded decomposeModel constant, defaulting to \"medium\" when Tier is empty. Remove the decomposeModel constant.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jzqqu","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add Tier field to DecomposeInput and use it in Pipeline.Decompose","updated_at":"2026-02-22T20:30:10Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"internal/pipeline/types.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-22T20:32:41Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"c61239aa06fe289495d8c8f70c7c70a64f7acbe05bd45c2f5d365e08cbd5ddc7","created_at":"2026-02-22T20:30:07Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jzqqu.1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add Tier to DecomposeInput","updated_at":"2026-02-22T20:32:41Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"internal/pipeline/decompose.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-22T20:35:32Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f7a86c98f15e11572488f745091ae469fdf34f4afabd9184457ae1520d305add","created_at":"2026-02-22T20:30:08Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-jzqqu.2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Use input Tier in Pipeline.Decompose with medium fallback","updated_at":"2026-02-22T20:35:32Z","waiters":"","wisp_type":"","work_type":""} @@ -1875,6 +1892,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T09:47:45Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"46a7ac132f7468ce8bf7a2df8b976932df756106b65ac5613985aa80c768a435","created_at":"2026-02-19T03:20:46Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"After the TDD cycle loop completes in internal/runner/process_methodology.go or internal/runner/run_iteration.go, populate bc.Result coverage fields from tracker state. When criteria are uncovered or untestable, add a bead comment via r.beads.AddComment with tracker.Summary. Log coverage summary to runner output. Add tests in the corresponding test file.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-u2pch","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Report coverage results to bead after TDD cycle loop","updated_at":"2026-02-20T09:47:45Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"All three build templates render Recent Validation Issues section when failures are present\nSection is completely omitted when no failures exist\nAll three templates include self-check guidance in their Instructions section","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-14T19:26:34Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f4fe78260b0e55ccbb52373a0b58af6534d0838f29957a4e37e135d3d9ea964f","created_at":"2026-02-12T17:55:41Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add conditional Recent Validation Issues section to all three build templates (PROMPT_build.md, PROMPT_atdd_build.md, PROMPT_tdd_build.md) between Learnings and Project Context sections. Add self-check guidance to Instructions sections instructing agents to run validation commands before completing. Template-only changes with tests.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-u2zx","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update build templates with validation failures section and self-check guidance","updated_at":"2026-02-14T19:26:34Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-10T11:43:18Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f04820301edb53f56a2942001bc9d2cba7975e2174e395da69317c13dce38619","created_at":"2026-02-10T11:28:18Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Run gromit add for each skipped test that describes desired but unimplemented behavior:\n- run_scope_acceptance_test.go: scope flags (--spec, --epic) integration with runLoop()\n- review_scope_acceptance_test.go: 2 skipped tests about --spec flag priority and no-matching-beads handling\n- filtered_hash_eviction_acceptance_test.go: 2 skipped tests about single-save optimization and archived learnings\n\nThen delete cmd/gromit/run_scope_acceptance_test.go entirely (all 11 tests are t.Skip).\n\nAcceptance criteria:\n- Backlog ideas captured via gromit add for skipped test behaviors\n- cmd/gromit/run_scope_acceptance_test.go deleted\n- go test ./cmd/gromit/... passes","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-u3z9","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Capture skipped test ideas in backlog and delete entirely-skipped files","updated_at":"2026-02-10T11:43:18Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"14ace8318bf6ff1f8a0e75fa0d81554f508297cd4d354b63872e63c4e28a4b7a","created_at":"2026-03-07T20:41:53Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add tests to internal/v2/prompt/assembler_test.go: assemble full build prompt, verify size within expected range, ShapeReport shows what was trimmed and by how much.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-u4837","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Add integration tests for full prompt shaping pipeline","updated_at":"2026-03-07T20:41:53Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"go test ./... and go vet ./... and go build ./... all pass with no errors or warnings\nExisting JSONL files without prompt_diagnostics parse without errors producing nil diagnostics fields\nRendered prompt strings are identical before and after diagnostics instrumentation is added","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-19T23:09:49Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"186b345822dacc5b332a6c2a360894cb1056a0af5a1ab468dd09f4d661b50f2e","created_at":"2026-02-19T19:42:49Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Run go test ./..., go vet ./..., and go build ./... to confirm all quality gates pass. Verify existing JSONL files without prompt_diagnostics deserialize cleanly. Confirm prompt rendering output is byte-identical with and without diagnostics capture. Verify gromit stats (without --prompt-tokens) output is unchanged.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-u4aqq","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Final verification: all tests pass and backward compatibility confirmed","updated_at":"2026-02-19T23:09:49Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-06T13:55:23Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4bec8252ddb2bf2a7ebebcc3162eee8b16423a8991ab864afcd7e2e946bcfb19","created_at":"2026-03-06T11:59:10Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Spec criterion 4 requires generating acceptance tests before bead execution. The accept stage runs after beads as a gate. The authoring phase is absent. Either implement a pre-bead acceptance authoring stage, or update the spec to document this as a deliberate design choice.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-u5w97","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add pre-bead acceptance test authoring or update spec","updated_at":"2026-03-06T13:55:23Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:30Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"7c1c3d54ec5e2b2eb9232b53646a2c4679bb04c13ef813872e6b3af4d7fc6525","created_at":"2026-02-13T12:29:13Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"TestMultipleRateLimitHits_WithoutInterveningEvent in internal/logger/stream_rate_limit_recovery_test.go asserts recoveryMs \u003c= 150 but has no lower-bound check. If the implementation regressed to always return 0, the test would still pass. Add a minimum assertion (e.g., recoveryMs \u003e= 10) to verify recovery is actually being measured, not silently returning zero.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-u6i7","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add lower-bound assertion to rate limit recovery test","updated_at":"2026-03-02T19:42:30Z","waiters":"","wisp_type":"","work_type":""} @@ -1919,6 +1937,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-22T07:47:55Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"42fb66ec807ef4f863c4c408f696fade4c5f7daa42e1d41df81da79d9b2eb2dd","created_at":"2026-02-22T06:34:13Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"In internal/pipeline/execute/build.go, update Build.Run() to detect when methodology=TDD AND config.Methodology.FreshContextPerCycle is true. When both conditions hold, delegate to the injected TDDCycleRunner instead of the single StreamRun() call. Add a tddCycleRunner field to the Build struct and a setter/constructor option to inject it. When TDDCycleRunner is nil and fresh-context is requested, fall back to the existing single-invocation TDD path. Return the aggregated PhaseMetrics in pipeline.Output.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uslyx","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Wire TDD fresh-context detection into Build.Run() to delegate to TDDCycleRunner","updated_at":"2026-02-22T07:47:55Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T14:14:48Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a17b7758413e5fff12cd3df4b30b58218437de45f48badef1a6913892b826f5b","created_at":"2026-03-07T05:55:34Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create cmd/gromit/debug2.go that implements the debug2 command for diagnosing failures from preserved worktree branches. Implement helper functions: resolveDebug2Worktree to look up the spec worktree, readDebug2EventLog to parse JSONL events, findFailureEvent to identify failure points, and buildDebug2Prompt to assemble diagnostic context. Wire Cobra command with positional spec-name argument. Invoke LLM session in the worktree for diagnosis. Handle LEARNINGS entries and systemic recommendations.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-utelc","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement debug2 command for diagnostics","updated_at":"2026-03-07T14:14:48Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:30Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d201eaabe8f3ebbbe92c0e52690258d4fa5c23e81ebae3e04574fd35409f5dde","created_at":"2026-02-13T05:35:36Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"internal/pipeline/decompose_test.go:682 has TestDecomposeWorkflow_RespectsContextCancellation with t.Skip('Context cancellation not yet implemented'). Per RULES.md: 'Do not write tests with t.Skip() for scenarios that can't run in the test environment. Every committed test must be runnable.' Either implement context cancellation support and the test, or delete the test and capture 'add context cancellation to Decompose' as a backlog item.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uugq","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Remove t.Skip test in decompose_test.go for context cancellation","updated_at":"2026-03-02T19:42:30Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:28:53Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"6d822499634189752fd8da78a8bf4634bdd29cfc7d17a70fb58ad2a89ac0e9cc","created_at":"2026-03-07T20:41:52Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add tests to internal/v2/routing/tier_test.go: verify methodology.phase_models overrides are read correctly, abstract tier maps to correct provider models, fallback behavior works.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uulh1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add unit tests for tier resolution","updated_at":"2026-03-07T21:28:53Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T09:29:26Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"1cca933a014b05c8238b5c52bbecd541fff4bb8e89f07613fecc69321189ba18","created_at":"2026-02-06T07:58:04Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add extra newlines/separator between iterations for visual clarity during ralph run.\n\nImplementation:\n- Print a blank line or light separator between iterations in the run loop\n- Ensures each iteration's output is visually distinct\n\nAcceptance criteria:\n1. Visible separation between iterations in terminal output\n2. No separator before first iteration or after last\n\nKey files: internal/runner/runner.go","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uv3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add visual separation between iterations in run output","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T18:23:55Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"364c2c8ed29804a533c1e4ff7bedac45acf522adcc9c3f82a8799e55c6fc87e8","created_at":"2026-02-07T03:16:47Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"The analyze prompt currently tells Claude to set learning to null if not 'generalizable'. This is too strict — task-specific patterns (like 'this package uses X convention' or 'tests in this area need Y setup') are valuable as provisional learnings. Change the prompt to: (1) encourage capturing any pattern that might help future tasks, (2) explicitly list examples of useful learnings (codebase conventions, test patterns, common gotchas), (3) only set null if the failure was truly one-off (e.g. typo). Keep the conciseness requirement (1-2 sentences). AC: prompt updated, examples added, null guidance narrowed.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uvl","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Soften PROMPT_analyze.md learning extraction criteria","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Each failing criterion produces one bead with spec:\u003cspecName\u003e label and title 'Fix: \u003ccriterion name\u003e'\nBead description includes criterion name, evidence, and fix direction\nCreation is capped at 5 beads; additional failures are skipped with a log message","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-20T12:53:43Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3cb731c6086bd554e3809db4ac53b7bcc413b0d6544867e99c0386ed6be23024","created_at":"2026-02-18T16:58:06Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/specgate/synthesize.go with BeadCreator interface and SynthesizeFixBeads function. Create internal/specgate/synthesize_test.go covering 0, 1, 5, and 6+ failure scenarios.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uwag","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement SynthesizeFixBeads in specgate package","updated_at":"2026-02-20T12:53:43Z","waiters":"","wisp_type":"","work_type":""} @@ -1930,8 +1949,10 @@ {"acceptance_criteria":"internal/validate/complexity.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T03:28:21Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"6277ba1aac449e0ca79010f2a394cbb10b3e3b56231b0721e7bfa7339c7b50a2","created_at":"2026-02-23T02:05:36Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uzrhu.1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add complexity result model types and scoring entrypoint","updated_at":"2026-02-23T03:28:21Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"internal/validate/complexity.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T03:25:32Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4db0ae807325918d03855f166747060bb2eca6e75fc7bea8e527bfea6494af5b","created_at":"2026-02-23T02:05:37Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uzrhu.2","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement high-complexity heuristic signals and reason generation","updated_at":"2026-02-23T03:25:32Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"internal/validate/complexity_test.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-23T03:21:32Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"87187ea69459c3e7130cc2ebe6353e136a9e2f2e906c338d8655a686383b30ca","created_at":"2026-02-23T02:05:38Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-uzrhu.3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add unit tests covering each complexity signal and classification outcomes","updated_at":"2026-02-23T03:21:32Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:32:30Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"abc3a3012109de29cb9082bd16866f169f8fbcc1897f5aecaed767ecf0179912","created_at":"2026-03-07T20:41:51Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/prompt/assembler.go: implement loadBaseInstructions(phase string) that filters RULES.md to phase-relevant sections. Apply per-phase character caps (build: 12800, red: 8500, etc.) based on phase parameter.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-v1j2u","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add phase filtering to rules loading in PromptAssembler","updated_at":"2026-03-07T21:32:30Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Subsumed by ralph-runner-dt4 (board) and ralph-runner-fqx (queue)","closed_at":"2026-02-06T07:09:32Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"d2ee1a7ca532daea88d748d09719c8010934170c56d89e322e7572295a65a5f3","created_at":"2026-02-06T06:54:30Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add a way to see all beads and a count of open beads from the ralph CLI. Could be an enhancement to 'ralph status' or a new subcommand. Should show:\n- List of open beads with priority and title\n- Total count of open vs closed beads\n- Current next-up bead","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-v1t","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add bead list and open count to CLI","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-06T17:13:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3ba9a649ac2f41473966988530a4451632c8acb178bdd2d099998efefc716c69","created_at":"2026-03-06T16:53:21Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"The v2 validate stage uses a noopValidationRunner that always returns nil. Need to create a real ValidationRunner implementation that executes shell commands (adapting v1's defaultCmdRunner from internal/runner/cmd_run.go). The ValidationRunner interface is simple: Run(ctx context.Context, command string) error. Wire it into loop.NewRun2LoopComponents replacing the noop stub.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-v4460","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"v2-run-loop","status":"closed","target":"","timeout_ns":0,"title":"Wire real validation command runner in v2 loop","updated_at":"2026-03-06T17:13:02Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:37:00Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"1ee6fb4357ef671b86c77ecaa14423352352478bbad9062949d4fed8e2948063","created_at":"2026-03-07T20:41:52Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add tests to internal/v2/loop/topo_test.go: DAG ordering respects DependsOn, cycle detection returns error, beads with no dependencies come first, stable tie-breaking.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-v46qz","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add unit tests for topological sort","updated_at":"2026-03-07T21:37:00Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-06T00:22:35Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"24ef1960a3c4f0fa0a81dbad70645bbf6ea50da7900e55588668e2085211fe2f","created_at":"2026-03-05T20:31:25Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create internal/v2/adapter/tasktracker/bd.go implementing TaskTracker using bd CLI, adapting patterns from internal/bead/. NextBead queries bd for next open bead with dependency info. CreateBead creates bead with title, description, priority, gen:N label, and dependency declarations. CloseBead marks bead closed. QueryBeads filters by labels, status, parent. Add bd_test.go.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-v6h8e","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement bd TaskTracker adapter","updated_at":"2026-03-06T00:22:35Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Extended diagnostics data structure for source-bucket attribution\nRender-method population of bucket fields by prompt type\nUpdated diagnostics tests validating bucket fields and compatibility semantics","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:39Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4261e8fc8735cc46a51d17b4ff81838b96b4454d9c80599132d40b897c4dbd45","created_at":"2026-02-24T04:25:15Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Extend prompt diagnostics in internal/prompt/diagnostics.go and internal/prompt/render_methods.go to report prompt-size attribution by source buckets. Cover template, rules, learnings, tool output, and context payload contribution where available. Update diagnostics tests to assert stable bucketed output.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-v6q0g","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add prompt diagnostics source buckets","updated_at":"2026-03-02T19:42:39Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Template renders experiment evaluation when active experiment exists\nTemplate includes experiment recommendation instructions in task section\nTemplate omits experiment section when no active experiment","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T01:15:47Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e9636f89d2d96d3553bc4c5d781afacb000058215966c1712d5883ef61a0d853","created_at":"2026-02-07T23:32:16Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add Active Experiment Evaluation section to .gromit/templates/PROMPT_retro.md showing experiment details and baseline vs current metrics comparison. Update Task section to add Efficiency Analysis item with Five Whys guidance. Add experiment recommendation instructions. Make conditional on .Experiment field.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-v7ft","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add experiment evaluation to retro template","updated_at":"2026-02-08T01:15:47Z","waiters":"","wisp_type":"","work_type":""} @@ -1977,6 +1998,7 @@ {"acceptance_criteria":"Status output includes model performance section positioned between Health and Recommendation\nSection displays when project logs contain model stats data\nMissing or empty stats are handled gracefully without errors or empty sections","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-11T15:34:44Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f760260f808fcad7cbff67f6a6752b7f6b098122f43fad958f03c4f86f6f5dd1","created_at":"2026-02-11T08:24:54Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify Runner.Status() in internal/runner/runner.go to call logger.ReadModelStats with r.cfg.Paths.Logs and display formatted model performance section between Health and Recommendation sections using formatModelPerformance. Handle errors gracefully.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w0en","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Wire model stats into gromit status","updated_at":"2026-02-11T15:34:44Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-10T18:17:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"f4b69bdc4cb0a5c90357e5c9a1aa731f98182f0bb85ddae06706909bc1784b25","created_at":"2026-02-08T07:39:59Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"The retro template has conditional branches ({{- if .Experiment }}, {{- if .Efficiency }}, {{- if .RunStats.Total }}) that render different content depending on whether Experiment/Efficiency are nil. The populated-data test covers the non-nil path. This test covers the nil/empty path to ensure the template renders without error when no experiment is active.\n\nAdd a test in internal/retro/retro_test.go that:\n1. Writes the real PROMPT_retro.md template to a temp dir\n2. Calls renderPrompt with Experiment=nil, Efficiency=nil, and zero RunStats\n3. Asserts no error and the output does NOT contain experiment-related sections\n\nAcceptance criteria:\n- Test calls renderPrompt with nil Experiment and nil Efficiency\n- Test asserts no template execution error\n- Test verifies experiment metric sections are absent from output","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w0lo","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add renderPrompt test covering nil Experiment branch","updated_at":"2026-02-10T18:17:02Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Recovery loop respects validation phase context deadline and exits with phase timeout classification\nPer-command timeouts still trigger command-level timeout handling as before\nTests cover interaction between phase timeout cap and command timeout","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-18T13:21:44Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"cb35e5b8e75aef01a3061609956ab523df68da47bd7625a12e1887c42fb883cc","created_at":"2026-02-16T18:12:01Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/runner/validation/runner.go to respect validation phase context deadline across full validation flow including recovery loop, while preserving per-command validation.command_timeout behavior. Update validation_test.go with tests covering phase timeout cap, command timeout interaction, and recovery loop deadline handling. Expand process_methodology_context_test.go with validation phase budget scenarios.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w0z6","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Enforce validation phase timeout budget for recovery loop","updated_at":"2026-02-18T13:21:44Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a4e66cb1566142452e25b6f941a25711e327d73e1e9c583284ef62ddd2d485f3","created_at":"2026-03-07T20:41:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/loop/bead_loop.go to call Router.Select(phase, tier) before each stage invocation. Populate StageRequest.Model and StageRequest.Provider fields. Handle escalation by incrementing RetryContext.EscalationLevel on failure and re-calling Router.Select with higher tier.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w1200","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Wire Router into bead loop routing seam","updated_at":"2026-03-07T20:41:50Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-03T18:50:04Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"44f5682f3ca555a9d5aed1614acf173a06ace84455142174bdfe7daf7b435bdb","created_at":"2026-03-03T04:24:02Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Delete 5 files from internal/runner/ that contain only fresh-context wiring: callbacks_tdd.go, callbacks_tdd_test.go, callbacks_tdd_missing_outputs_test.go, tdd_pipeline_adapter.go, tdd_pipeline_adapter_test.go. These files depend on the tdd package deleted in the previous bead.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w1dux","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Delete runner fresh-context adapter and callback files","updated_at":"2026-03-03T18:50:04Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"golang.org/x/sync appears in go.mod\ngo.sum is updated\ngo build ./... still passes","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"golang.org/x/sync/errgroup already in go.mod and imported in process.go","closed_at":"2026-02-08T15:43:23Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"80555839ddcb7476ea3c07d9bff67f0bd7aab315fa8984b5604d5ff40bf2073f","created_at":"2026-02-08T10:41:18Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Run 'go get golang.org/x/sync' to add errgroup to go.mod and go.sum. No code changes — dependency only.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w1sv","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add golang.org/x/sync/errgroup dependency","updated_at":"2026-02-08T15:43:23Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T09:05:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"449e36ef274e5766d5487287ee4d0b3796f9eee220c584ea9f3f920d61f01a33","created_at":"2026-02-06T07:43:51Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Go CLI command that launches an interactive Claude Code session with feature context pre-loaded.\n\nUsage: ralph plan \"Add tmux integration\"\n\nImplementation:\n- Takes feature description as argument\n- Reads relevant codebase context (open beads, project structure)\n- Launches: claude --append-system-prompt \"Feature to plan: [description]. [context]. Use the ralph-plan skill.\"\n- Passes through stdin/stdout/stderr for interactive session\n- cmd.Stdin = os.Stdin, cmd.Stdout = os.Stdout\n\nAcceptance criteria:\n1. ralph plan launches interactive Claude Code session\n2. System prompt includes feature description and codebase context\n3. References ralph-plan skill in the prompt\n\nKey files: cmd/ralph/plan.go (new), internal/claude/claude.go (reference for exec pattern)","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w1t","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"ralph plan CLI command: launch Claude Code with feature context","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} @@ -1992,6 +2014,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-05T11:12:31Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e8cd9956be0733ae6f3f3b46cc53f5e8851844ab7a596c5da689db16d3b9ec52","created_at":"2026-03-04T12:11:33Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"internal/bead/bead.go has ~90 lines of duplicated subprocess setup between runWithEnv (lines 504-548) and runWithEnvCombinedOutput (lines 609-652). Extract shared subprocess lifecycle into a helper that accepts an output-configuration parameter.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w72th","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Deduplicate bead.Client runWithEnv and runWithEnvCombinedOutput","updated_at":"2026-03-05T11:12:31Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Test verifies pruning stale hashes returns true and updates slice correctly\nTest verifies no-op case returns false and leaves slice unchanged\nTest verifies nil receiver and edge cases do not panic","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T09:54:26Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3ce030519a9d4f7452a1baf403dd4f7e722d09505f31847475631ed54f696087","created_at":"2026-02-08T08:18:27Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add comprehensive unit tests for ReconcileFilteredHashes in internal/state/state_test.go. Cover pruning stale hashes, no-op when all match, empty current set prunes all, empty existing hashes, and nil receiver safety.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w7cn","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add unit tests for ReconcileFilteredHashes","updated_at":"2026-02-08T09:54:26Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"IsValidateEnabled returns true when Validate is nil, respects explicit false\nSetDefaults sets MaxValidationRetries to 2 when zero\nYAML round-trips correctly","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-14T04:54:22Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"206b3ac0109861e63a811db23f6ea8b56e3c866f8368640e2846c526352ff059","created_at":"2026-02-12T10:02:23Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add DecomposeConfig struct to internal/config/config.go with Validate *bool and MaxValidationRetries int fields, IsValidateEnabled method, and wire into Config struct and SetDefaults. Extend internal/config/config_test.go for defaults and YAML parsing. Add decompose section to gromit.yaml with comments.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w7qn","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add DecomposeConfig to config with tests","updated_at":"2026-02-14T04:54:22Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:40:04Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"cb301450ad10f6768479a16bbab5a77b8ad0ab52218419b55c1b7437efd7d48d","created_at":"2026-03-07T20:41:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add gap analysis logic to internal/v2/loop/spec_loop.go (or new internal/v2/gap/gap.go): diff worktree HEAD against last successful bead's commit, identify completed beads whose files have changed, flag them for revalidation.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w83pd","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement gap analysis for resume","updated_at":"2026-03-07T21:40:04Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T06:13:50Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"924a126611b45e2811450a56750b43b101a4b086cd0b6fc9cb915c4a29aaa627","created_at":"2026-03-07T05:55:33Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create StageCommitter in internal/v2/pipeline/stage_committer.go that wraps a GitCommitter interface and creates per-stage structured commits. Implement CommitStage method that calls git status, skips commits when nothing changed, and uses FormatCommitMessage to produce [bead:ID/stage/iter:N] or [spec/stage/iter:N] format messages. Include local GitCommitter interface definition to avoid circular imports.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w8std","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement StageCommitter for structured git commits","updated_at":"2026-03-07T06:13:50Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"MaxConsecutiveSkips field added to LoopConfig struct with yaml:\"max_consecutive_skips\" tag\nsetDefaults() sets MaxConsecutiveSkips to 3 when value is 0\nField is parsed correctly from YAML configuration","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T01:27:14Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"839470f6513bbd713ea0a108008627bef62b5ec176997f6f9347e5c5a476d868","created_at":"2026-02-07T23:36:10Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add MaxConsecutiveSkips int field to LoopConfig struct in internal/config/config.go with yaml tag max_consecutive_skips. Add default of 3 in setDefaults() when value is 0, following the StuckBeadThreshold pattern at lines 229-230.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w971","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add MaxConsecutiveSkips to config","updated_at":"2026-02-08T01:27:14Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Tests verify defaults are set when git section is omitted from YAML\nTests verify explicit YAML git section parses correctly with auto_push: false\nTests verify IsAutoPushEnabled() returns expected values for nil, true, and false","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-07T23:40:15Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"acc89c841b9dcc3e67751158029586634dd4a3145b1bf3ae000182c9da41d228","created_at":"2026-02-07T22:34:44Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add table-driven tests to internal/config/config_test.go covering GitConfig defaults, YAML parsing with explicit git section, and IsAutoPushEnabled() helper method behavior. Test nil-safety and all config combinations.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-w9rs","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add GitConfig tests","updated_at":"2026-02-07T23:40:15Z","waiters":"","wisp_type":"","work_type":""} @@ -2034,7 +2057,7 @@ {"acceptance_criteria":"Client.Run returns timeout error for hung command\nTimeout behavior is consistent and reliable\nTest replaces document-only placeholder with real coverage","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:27Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"29eec3e2b535ec3f993bc055090c684c14fdbe1aad21788ed1fc53c6db357e4a","created_at":"2026-02-19T14:47:20Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Cover that claude Client.Run returns/handles timeout for hung command and times out consistently; current document-only test placeholder should be replaced by reliable coverage.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-x0dqh","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add integration coverage for claude Run timeout behavior","updated_at":"2026-03-02T19:42:27Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Retro filters per-bead stats by bead ID set\nRetro filters log entries by bead ID set\nRetro without filter behaves identically to current behavior","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-10T10:14:42Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"e01408b4375af699401a5941d0aa0e0333261ff59aad2f52427af019a707b438","created_at":"2026-02-08T17:00:34Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/retro/retro.go to accept optional bead ID filter set. Filter ReadPerBeadStats() results to only include stats for beads in the filter set. Filter ReadAllLogs() results to only include log entries where BeadID is in the filter set. When no filter is set, preserve current behavior.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-x316","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Update retro logic to filter by bead IDs","updated_at":"2026-02-10T10:14:42Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"1. Three input modes: no args (picker for unrefined backlog), idea-* ID, or ad-hoc text\n2. Picker filters backlog to Status != \"refined\" items\n3. System prompt includes idea text, specs dir path, gromit-refine skill reference\n4. After Claude exits, scans .gromit/specs/ for files modified after session start\n5. If new spec found from backlog item: updates backlog with Status:\"refined\" and SpecName\n6. Uses same exec.Command(\"claude\", \"--append-system-prompt\", ...) launch pattern","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T14:02:32Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"fbb9e5ae7bfc0888a5992c69321e7310e2974207bfb0d032a42c34e6c8289efb","created_at":"2026-02-06T13:41:43Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Rewrite cmd/gromit/refine.go. Three input modes: no args (picker for unrefined backlog items), backlog ID (idea-* pattern), or ad-hoc string. System prompt includes idea text, specs dir path, gromit-refine skill reference. After Claude exits, scan .gromit/specs/ for new files and mark backlog item as status:refined. Also depends on Task 4 (refine skill, gromit-0zh).","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-x3n","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"Cross-dependencies: also depends on gromit-0zh (refine skill). Skill bead should be completed first.","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Rewrite refine command for spec output","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} -{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a78e6b5c828a5ff8f75d2828ecf03231a50d14c692fa4e71055ee3057248fb5a","created_at":"2026-03-07T15:06:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Run comprehensive test suite across all v2 packages (routing, stage, loop, prompt, remediation). Verify no regressions in existing tests. Run linter (golangci-lint) to check for code quality issues. Perform final cleanup: remove any debug logging, verify error messages are clear, ensure all newly added code follows project patterns from CLAUDE.md.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-x5abw","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Final verification and cleanup","updated_at":"2026-03-07T15:06:50Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T20:33:06Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a78e6b5c828a5ff8f75d2828ecf03231a50d14c692fa4e71055ee3057248fb5a","created_at":"2026-03-07T15:06:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Run comprehensive test suite across all v2 packages (routing, stage, loop, prompt, remediation). Verify no regressions in existing tests. Run linter (golangci-lint) to check for code quality issues. Perform final cleanup: remove any debug logging, verify error messages are clear, ensure all newly added code follows project patterns from CLAUDE.md.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-x5abw","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Final verification and cleanup","updated_at":"2026-03-07T20:33:06Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-06T11:01:49Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4ce9af21536e021965075c505d59b5bf5ce7c7d55517dfc556b3d6241a16cf5d","created_at":"2026-02-06T09:29:44Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add applyReviewResult method to Runner: creates beads from BeadsToCreate (with from-review label, reviewer-assigned priority) and BacklogItems (with from-review + backlog labels, P2). Add buildReviewBeadLabels and buildBacklogLabels helpers. See docs/plans/2026-02-06-review-system-plan.md Task 10. Depends on task 9.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-x5j","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement applyReviewResult for bead creation from findings","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- [ ] New BeadStats struct: BeadID, TotalAttempts, Failures, LastAttempt time.Time, LastError string\n- [ ] New ReadPerBeadStats(logsDir string) (map[string]BeadStats, error) function in logger.go\n- [ ] Scans all run-*.jsonl files and groups entries by bead_id\n- [ ] Correctly counts failures vs successes per bead\n- [ ] Tracks most recent attempt timestamp and error message\n- [ ] Handles empty/missing logs dir gracefully (returns empty map, nil)\n- [ ] Unit test covers: empty dir, single log, multiple logs, mixed success/failure\n- [ ] go test ./internal/logger/... passes","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Breaking down into smaller tasks","closed_at":"2026-02-05T19:58:49Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b876f756a1d5e4eb598cb2abc7f7ffe7faf40689ce0619f1bf089a6406226776","created_at":"2026-02-05T19:44:37Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add a function that scans JSONL log files and returns per-bead failure statistics. This enables both stuck detection in the runner and pattern analysis in retro.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-x72","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add ReadPerBeadStats function to logger package","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"ReadyWithLabel passes --label flag to bd ready command\nMethod excludes epic-type beads from results\nReturns nil with no error when no beads match label","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T12:05:51Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"de4f7a916f1ea32a2869dae9c4327e0f84c26b8ff79497ba0101c9685c266aff","created_at":"2026-02-08T08:10:56Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Add ReadyWithLabel method to bead.Client in internal/bead/bead.go that calls 'bd ready --json --limit 10 --label \u003clabel\u003e' and parses output excluding epic types. Add method to BeadClient interface in internal/runner/interfaces.go.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-x9bq","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add ReadyWithLabel to bead client","updated_at":"2026-02-08T12:05:51Z","waiters":"","wisp_type":"","work_type":""} @@ -2172,6 +2195,7 @@ {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Breaking down into per-file constant extraction beads","closed_at":"2026-02-05T19:33:36Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"3672da4299f822f7a0231d1044576cae05b48f7bcd28d8e9ee8dead2a6dfa874","created_at":"2026-02-05T18:38:54Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Several magic values are hard-coded throughout the codebase.\n\n## Values to extract\n- `internal/analyzer/analyzer.go:68` - maxLen := 8000 (truncation limit)\n- `internal/learnings/learnings.go:86` - 0.7 (similarity threshold)\n- `internal/runner/runner.go:443` - 60 * time.Second (heartbeat interval)\n\n## Fix\nMove to package-level constants with documentation explaining the values.\n\n## Files\n- internal/analyzer/analyzer.go\n- internal/learnings/learnings.go\n- internal/runner/runner.go","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-zpf","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Extract magic values to named constants","updated_at":"2026-02-07T18:29:38Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"Remove dead code: convertToClaudeResult, processStreamJSON, contains/containsString","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"stale: db reset","closed_at":"2026-03-02T19:42:29Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"ba5b13e7bc4120837774b8971455c039ab964b50e11926a634320fb077e23dfe","created_at":"2026-02-18T18:37:09Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Three dead code sites: (1) internal/provider/claude.go:86-94 convertToClaudeResult is never called, (2) internal/claude/claude.go:439-441 processStreamJSON wraps processStreamJSONWithCost but is never invoked, (3) cmd/gromit/init.go:218-229 custom contains/containsString reimplements strings.Contains. Delete all three and replace init.go usage with strings.Contains.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-zpqdj","is_template":0,"issue_type":"bug","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dabrams@example.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Remove dead code: convertToClaudeResult, processStreamJSON, contains/containsString","updated_at":"2026-03-02T19:42:29Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-06T10:09:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"dbe5ca0cb41a3c87d6cabfc814a730a56aa17de154efa29089a7c9ed3fb000c5","created_at":"2026-03-05T20:31:25Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Create cmd/gromit/run2.go with run2 subcommand accepting a spec file path arg. Loads spec and checks dependencies via spec.CheckDependencies. Loads config. Wires Claude LLM, bd TaskTracker, GitHub Presenter, and exec Git adapters. Creates SpecLoop with wired adapters. Subscribes CLI progress and log writer event consumers. Runs spec loop. Exits 0 on success, non-zero on failure. Register in main.go.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-zq7iy","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement gromit run2 CLI command","updated_at":"2026-03-06T10:09:07Z","waiters":"","wisp_type":"","work_type":""} +{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-07T21:45:13Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"69ab5b9595f2bc3251228133e4eb1a78b4af3e2edbd4fd821a97a62d811ed4f7","created_at":"2026-03-07T20:41:50Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Modify internal/v2/loop/spec_loop.go to run validation commands scoped to flagged beads. If validation fails, mark bead incomplete and re-queue it. Execute this before starting bead loop on resume.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-zrrn7","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":1,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement selective revalidation in spec loop","updated_at":"2026-03-07T21:45:13Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-02-08T07:27:05Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"8d5e87550f7d79d2c4d5f9dca98a75905b5c86a03d4686edf086b574e51cdbb8","created_at":"2026-02-07T22:42:29Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Runner tests that don't specifically test precheck (e.g. TestRunWithMocks_SkipsStuckBeads, TestRunWithMocks_ClosesBeadOnSuccess at interfaces_test.go) don't set Precheck config. Since NewRunnerWithDeps doesn't call setDefaults(), precheck is disabled by default (nil Enabled pointer → IsEnabled() returns true, but empty Model means Claude.Run gets empty model string). This works accidentally but could break if defaults change. Consider either: (1) having NewRunnerWithDeps call setDefaults(), or (2) explicitly disabling precheck in tests that don't test it.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-zt4n","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"dan@danabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Add precheck config to non-precheck runner tests to prevent accidental precheck execution","updated_at":"2026-02-08T07:27:05Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-06T10:09:07Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"754b0197d796e88d63b4463daf4f7fbc15e281dd38b81842da8c8bc8c7d6ef38","created_at":"2026-03-06T06:40:24Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"Implement internal/v2/event/emitter.go with a thread-safe Emitter. Supports Subscribe(func(Event)) and ensures each emitted event fans out to all subscribers without blocking on panicking or slow handlers. Tests should cover concurrency, backpressure isolation, and panic resilience. Follow TDD (red/green) workflow.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-zv7y1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":0,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Implement thread-safe event emitter","updated_at":"2026-03-06T10:09:07Z","waiters":"","wisp_type":"","work_type":""} {"acceptance_criteria":"- SpecflowStoreFactory and SpecBranchCreatorFactory are passed as parameters (e.g., on a deps struct or as function args) rather than mutated as globals\n- No package-level mutation of runner.* factory globals in main.go\n- Existing tests still pass\n- go test ./cmd/gromit/... passes\n- go vet ./... passes\n\nKey file: cmd/gromit/main.go","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"Closed","closed_at":"2026-03-04T19:10:33Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"a810d05e53db6ac3b0f9acaf48d6a285340c82d730823b1c34a9237071f23508","created_at":"2026-03-04T13:05:25Z","created_by":"Dan Abrams","crystallizes":0,"defer_until":null,"description":"main.go runLoop temporarily replaces runner.SpecflowStoreFactory and runner.SpecBranchCreatorFactory package-level globals, calls functions, then restores them. This is not thread-safe — if multiple goroutines execute runLoop concurrently, they race on the global state. Unlike gromit-4tt9w which covers test seam vars, this is production code in main.go.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"gromit-zvc8u","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"me@thedanabrams.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Pass SpecflowStoreFactory and SpecBranchCreatorFactory as parameters instead of mutating globals","updated_at":"2026-03-04T19:10:33Z","waiters":"","wisp_type":"","work_type":""} diff --git a/.beads/backup/labels.jsonl b/.beads/backup/labels.jsonl index 18d55817c..69a8ee07b 100644 --- a/.beads/backup/labels.jsonl +++ b/.beads/backup/labels.jsonl @@ -42,6 +42,9 @@ {"issue_id":"gromit-0uq","label":"spec:expanded-status"} {"issue_id":"gromit-0vxy","label":"spec:concurrent-interactive-worktrees"} {"issue_id":"gromit-0xbs","label":"spec:retro-efficiency-experiments"} +{"issue_id":"gromit-0xxdf","label":"estimated-files:2"} +{"issue_id":"gromit-0xxdf","label":"gen:0"} +{"issue_id":"gromit-0xxdf","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-0ypa","label":"from-review"} {"issue_id":"gromit-0zg","label":"spec:system-verification"} {"issue_id":"gromit-0zh","label":"spec:pipeline-stages"} @@ -64,10 +67,16 @@ {"issue_id":"gromit-18sei.1","label":"spec:decompose-low-complexity-bias"} {"issue_id":"gromit-18sei.2","label":"spec:decompose-low-complexity-bias"} {"issue_id":"gromit-18sei.3","label":"spec:decompose-low-complexity-bias"} +{"issue_id":"gromit-1b6ks","label":"estimated-files:2"} +{"issue_id":"gromit-1b6ks","label":"gen:0"} +{"issue_id":"gromit-1b6ks","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-1c2ii","label":"tdd:true"} {"issue_id":"gromit-1d0u","label":"spec:spec-level-atdd-execution"} {"issue_id":"gromit-1fjzj","label":"from-review"} {"issue_id":"gromit-1fjzj","label":"spec:concurrent-interactive-worktrees"} +{"issue_id":"gromit-1fuf8","label":"estimated-files:2"} +{"issue_id":"gromit-1fuf8","label":"gen:0"} +{"issue_id":"gromit-1fuf8","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-1gnya","label":"from-review"} {"issue_id":"gromit-1gnya","label":"scope-gate"} {"issue_id":"gromit-1gnya","label":"spec:review-spec-validation"} @@ -107,6 +116,9 @@ {"issue_id":"gromit-1zcnn","label":"gen:0"} {"issue_id":"gromit-1zcnn","label":"spec:immutable-pipeline"} {"issue_id":"gromit-1zx","label":"spec:review-system"} +{"issue_id":"gromit-20962","label":"estimated-files:3"} +{"issue_id":"gromit-20962","label":"gen:0"} +{"issue_id":"gromit-20962","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-20iy","label":"spec:pipeline-concrete-types"} {"issue_id":"gromit-20mha","label":"backlog"} {"issue_id":"gromit-20v4","label":"spec:state-staleness-protection"} @@ -392,6 +404,9 @@ {"issue_id":"gromit-61692.1","label":"tdd:true"} {"issue_id":"gromit-61692.2","label":"tdd:true"} {"issue_id":"gromit-61692.3","label":"tdd:true"} +{"issue_id":"gromit-61cbs","label":"estimated-files:2"} +{"issue_id":"gromit-61cbs","label":"gen:0"} +{"issue_id":"gromit-61cbs","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-62ro2","label":"bug"} {"issue_id":"gromit-62ro2","label":"from-review"} {"issue_id":"gromit-6426","label":"spec:optimize-bd-subprocess-calls"} @@ -484,6 +499,9 @@ {"issue_id":"gromit-791l5.2","label":"scope_decomp:bb7c5088fca01066"} {"issue_id":"gromit-79kjl","label":"estimated-files:2"} {"issue_id":"gromit-79kjl","label":"spec:tui-pipeline-view"} +{"issue_id":"gromit-7a6rt","label":"estimated-files:2"} +{"issue_id":"gromit-7a6rt","label":"gen:0"} +{"issue_id":"gromit-7a6rt","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-7dcu","label":"spec:multi-agent-phases"} {"issue_id":"gromit-7duv2","label":"spec:dynamic-code-map"} {"issue_id":"gromit-7e5o","label":"consistency"} @@ -744,6 +762,9 @@ {"issue_id":"gromit-8w81a","label":"spec:review-spec-validation"} {"issue_id":"gromit-8x2","label":"from-review"} {"issue_id":"gromit-8y6e","label":"spec:reduce-iteration-cost"} +{"issue_id":"gromit-8yarv","label":"estimated-files:2"} +{"issue_id":"gromit-8yarv","label":"gen:0"} +{"issue_id":"gromit-8yarv","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-8yl","label":"spec:status-json-staleness"} {"issue_id":"gromit-8ztwd","label":"estimated-files:4"} {"issue_id":"gromit-8ztwd","label":"gen:0"} @@ -813,6 +834,9 @@ {"issue_id":"gromit-9wmf9","label":"tdd:true"} {"issue_id":"gromit-9xmm1","label":"spec:prompt-token-accounting-diagnostics"} {"issue_id":"gromit-9xp","label":"complexity:low"} +{"issue_id":"gromit-9z0j1","label":"estimated-files:2"} +{"issue_id":"gromit-9z0j1","label":"gen:0"} +{"issue_id":"gromit-9z0j1","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-a1s","label":"spec:status-json-staleness"} {"issue_id":"gromit-a2j","label":"spec:retro-improvements"} {"issue_id":"gromit-a7wu","label":"spec:session-epilogue"} @@ -845,6 +869,9 @@ {"issue_id":"gromit-aqye","label":"spec:tskip-audit"} {"issue_id":"gromit-atgpc","label":"estimated-files:2"} {"issue_id":"gromit-atgpc","label":"spec:phase-cost-optimization"} +{"issue_id":"gromit-atr10","label":"estimated-files:2"} +{"issue_id":"gromit-atr10","label":"gen:0"} +{"issue_id":"gromit-atr10","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-au1yu","label":"estimated-files:2"} {"issue_id":"gromit-au1yu","label":"spec:auto-generate-acceptance-criteria"} {"issue_id":"gromit-avbc","label":"spec:retro-efficiency-experiments"} @@ -975,6 +1002,9 @@ {"issue_id":"gromit-csr","label":"spec:review-system"} {"issue_id":"gromit-ctgu","label":"spec:result-output-semantics"} {"issue_id":"gromit-ctpu","label":"spec:state-staleness-protection"} +{"issue_id":"gromit-cufx0","label":"estimated-files:2"} +{"issue_id":"gromit-cufx0","label":"gen:0"} +{"issue_id":"gromit-cufx0","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-cwk4z","label":"spec:runner-pipeline"} {"issue_id":"gromit-cx22d","label":"bug"} {"issue_id":"gromit-cx22d","label":"from-review"} @@ -988,6 +1018,12 @@ {"issue_id":"gromit-d0n0","label":"spec:pipeline-concrete-types"} {"issue_id":"gromit-d2aj2","label":"from-review"} {"issue_id":"gromit-d2aj2","label":"spec:runner-pipeline"} +{"issue_id":"gromit-d2rpx","label":"estimated-files:2"} +{"issue_id":"gromit-d2rpx","label":"gen:0"} +{"issue_id":"gromit-d2rpx","label":"spec:v2-loop-routing-and-resume"} +{"issue_id":"gromit-d4134","label":"estimated-files:2"} +{"issue_id":"gromit-d4134","label":"gen:0"} +{"issue_id":"gromit-d4134","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-d46r","label":"spec:status-bead-breakdown"} {"issue_id":"gromit-d6sl","label":"from-review"} {"issue_id":"gromit-d6sl","label":"refactor"} @@ -1144,6 +1180,9 @@ {"issue_id":"gromit-evne","label":"spec:parallel-interactive-command-worktrees"} {"issue_id":"gromit-evq","label":"bug"} {"issue_id":"gromit-evq","label":"from-review"} +{"issue_id":"gromit-ew1l8","label":"estimated-files:3"} +{"issue_id":"gromit-ew1l8","label":"gen:0"} +{"issue_id":"gromit-ew1l8","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-ewl","label":"spec:harden-fake-claude-writes"} {"issue_id":"gromit-eww","label":"complexity:low"} {"issue_id":"gromit-ex7","label":"complexity:low"} @@ -1158,6 +1197,9 @@ {"issue_id":"gromit-f2ng8.3","label":"tdd:true"} {"issue_id":"gromit-f37u5","label":"from-review"} {"issue_id":"gromit-f37u5","label":"tdd:false"} +{"issue_id":"gromit-f4ouz","label":"estimated-files:2"} +{"issue_id":"gromit-f4ouz","label":"gen:0"} +{"issue_id":"gromit-f4ouz","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-f4q0","label":"tdd:false"} {"issue_id":"gromit-f4t","label":"spec:gromit-prefix-migration"} {"issue_id":"gromit-f6bo","label":"spec:run-loop-reliability-speed"} @@ -1194,6 +1236,9 @@ {"issue_id":"gromit-fh4cu","label":"estimated-files:2"} {"issue_id":"gromit-fh4cu","label":"gen:0"} {"issue_id":"gromit-fh4cu","label":"spec:immutable-pipeline"} +{"issue_id":"gromit-fhp2q","label":"estimated-files:2"} +{"issue_id":"gromit-fhp2q","label":"gen:0"} +{"issue_id":"gromit-fhp2q","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-fj1ad","label":"estimated-files:2"} {"issue_id":"gromit-fj1ad","label":"spec:v2-run-loop"} {"issue_id":"gromit-fj9i","label":"spec:acceptance-runtime-optimization"} @@ -1461,6 +1506,9 @@ {"issue_id":"gromit-jsb","label":"complexity:low"} {"issue_id":"gromit-jsta","label":"spec:model-success-tracking"} {"issue_id":"gromit-juyb","label":"spec:multi-provider-routing"} +{"issue_id":"gromit-jv6ns","label":"estimated-files:2"} +{"issue_id":"gromit-jv6ns","label":"gen:0"} +{"issue_id":"gromit-jv6ns","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-jva","label":"from-review"} {"issue_id":"gromit-jva","label":"test-infrastructure"} {"issue_id":"gromit-jw28c","label":"spec:phase-1-2-token-efficiency-foundation"} @@ -1475,6 +1523,9 @@ {"issue_id":"gromit-jysme","label":"from-review"} {"issue_id":"gromit-jysme","label":"spec:decompose-complexity-estimation"} {"issue_id":"gromit-jz5w","label":"spec:runner-subpackage-split"} +{"issue_id":"gromit-jzbe3","label":"estimated-files:2"} +{"issue_id":"gromit-jzbe3","label":"gen:0"} +{"issue_id":"gromit-jzbe3","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-jzqqu","label":"estimated-files:2"} {"issue_id":"gromit-jzqqu","label":"spec:provider-parity-decompose-retro"} {"issue_id":"gromit-jzqqu.1","label":"estimated-files:2"} @@ -2250,6 +2301,9 @@ {"issue_id":"gromit-u2pch","label":"tdd:true"} {"issue_id":"gromit-u2zx","label":"spec:reduce-iteration-cost"} {"issue_id":"gromit-u3z9","label":"spec:acceptance-test-cleanup"} +{"issue_id":"gromit-u4837","label":"estimated-files:2"} +{"issue_id":"gromit-u4837","label":"gen:0"} +{"issue_id":"gromit-u4837","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-u4aqq","label":"spec:prompt-token-accounting-diagnostics"} {"issue_id":"gromit-u5w97","label":"spec:v2-run-loop"} {"issue_id":"gromit-u6i7","label":"from-review"} @@ -2307,6 +2361,9 @@ {"issue_id":"gromit-utelc","label":"spec:immutable-pipeline"} {"issue_id":"gromit-uugq","label":"from-review"} {"issue_id":"gromit-uugq","label":"test-gap"} +{"issue_id":"gromit-uulh1","label":"estimated-files:2"} +{"issue_id":"gromit-uulh1","label":"gen:0"} +{"issue_id":"gromit-uulh1","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-uv3","label":"complexity:low"} {"issue_id":"gromit-uwag","label":"spec:spec-acceptance-verification-loop"} {"issue_id":"gromit-uwuh","label":"complexity:low"} @@ -2320,7 +2377,13 @@ {"issue_id":"gromit-uzrhu.1","label":"spec:decompose-low-complexity-bias"} {"issue_id":"gromit-uzrhu.2","label":"spec:decompose-low-complexity-bias"} {"issue_id":"gromit-uzrhu.3","label":"spec:decompose-low-complexity-bias"} +{"issue_id":"gromit-v1j2u","label":"estimated-files:2"} +{"issue_id":"gromit-v1j2u","label":"gen:0"} +{"issue_id":"gromit-v1j2u","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-v4460","label":"spec:v2-run-loop"} +{"issue_id":"gromit-v46qz","label":"estimated-files:2"} +{"issue_id":"gromit-v46qz","label":"gen:0"} +{"issue_id":"gromit-v46qz","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-v6h8e","label":"estimated-files:2"} {"issue_id":"gromit-v6h8e","label":"spec:v2-run-loop"} {"issue_id":"gromit-v6q0g","label":"spec:phase-1-2-token-efficiency-foundation"} @@ -2367,6 +2430,9 @@ {"issue_id":"gromit-w0en","label":"spec:model-success-tracking"} {"issue_id":"gromit-w0lo","label":"complexity:low"} {"issue_id":"gromit-w0z6","label":"spec:phase-isolated-methodology-contexts"} +{"issue_id":"gromit-w1200","label":"estimated-files:2"} +{"issue_id":"gromit-w1200","label":"gen:0"} +{"issue_id":"gromit-w1200","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-w1dux","label":"estimated-files:5"} {"issue_id":"gromit-w1dux","label":"spec:remove-tdd-fresh-context"} {"issue_id":"gromit-w1sv","label":"complexity:low"} @@ -2389,6 +2455,9 @@ {"issue_id":"gromit-w72th","label":"refactor"} {"issue_id":"gromit-w7cn","label":"spec:filtered-hash-eviction"} {"issue_id":"gromit-w7qn","label":"spec:bead-scope-validation"} +{"issue_id":"gromit-w83pd","label":"estimated-files:2"} +{"issue_id":"gromit-w83pd","label":"gen:0"} +{"issue_id":"gromit-w83pd","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-w8std","label":"estimated-files:2"} {"issue_id":"gromit-w8std","label":"gen:0"} {"issue_id":"gromit-w8std","label":"spec:immutable-pipeline"} @@ -2729,6 +2798,9 @@ {"issue_id":"gromit-zpqdj","label":"from-review"} {"issue_id":"gromit-zq7iy","label":"estimated-files:2"} {"issue_id":"gromit-zq7iy","label":"spec:v2-run-loop"} +{"issue_id":"gromit-zrrn7","label":"estimated-files:2"} +{"issue_id":"gromit-zrrn7","label":"gen:0"} +{"issue_id":"gromit-zrrn7","label":"spec:v2-loop-routing-and-resume"} {"issue_id":"gromit-zt4n","label":"from-review"} {"issue_id":"gromit-zt4n","label":"test-gap"} {"issue_id":"gromit-zvc8u","label":"bug"} diff --git a/.gromit/v2/events.jsonl b/.gromit/v2/events.jsonl new file mode 100644 index 000000000..31fda1912 --- /dev/null +++ b/.gromit/v2/events.jsonl @@ -0,0 +1,193 @@ +{"schema_version":1,"timestamp":"2026-03-07T15:34:50.539989-05:00","type":"spec.started","spec_id":"v2-loop-routing-and-resume","worktree":"/Users/dabrams/gromit/.gromit/spec-worktrees/v2-loop-routing-and-resume"} +{"schema_version":1,"timestamp":"2026-03-07T15:41:52.723564-05:00","type":"bead.started","bead_id":"gromit-0xxdf","bead_title":"Add unit tests for gap analysis","iteration":1} +{"schema_version":1,"timestamp":"2026-03-07T15:41:52.723575-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-0xxdf","iteration":1} +{"schema_version":1,"timestamp":"2026-03-07T15:41:52.868575-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-0xxdf","iteration":1,"success":true,"duration":144998916} +{"schema_version":1,"timestamp":"2026-03-07T15:41:52.868582-05:00","type":"bead.completed","bead_id":"gromit-0xxdf","bead_title":"Add unit tests for gap analysis","iteration":1,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T15:41:52.868605-05:00","type":"bead.started","bead_id":"gromit-1b6ks","bead_title":"Add unit tests for Router ratio balancing and fallback","iteration":2} +{"schema_version":1,"timestamp":"2026-03-07T15:41:52.868606-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-1b6ks","iteration":2} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.007742-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-1b6ks","iteration":2,"success":true,"duration":139134708} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.007751-05:00","type":"bead.completed","bead_id":"gromit-1b6ks","bead_title":"Add unit tests for Router ratio balancing and fallback","iteration":2,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.007774-05:00","type":"bead.started","bead_id":"gromit-1fuf8","bead_title":"Add integration tests for gate re-queue on block","iteration":3} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.007777-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-1fuf8","iteration":3} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.149926-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-1fuf8","iteration":3,"success":true,"duration":142148250} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.149934-05:00","type":"bead.completed","bead_id":"gromit-1fuf8","bead_title":"Add integration tests for gate re-queue on block","iteration":3,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.149955-05:00","type":"bead.started","bead_id":"gromit-20962","bead_title":"Expand remediation runner signature for worktree propagation","iteration":4} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.149957-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-20962","iteration":4} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.22076-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-20962","iteration":4,"success":true,"duration":70802833} +{"schema_version":1,"timestamp":"2026-03-07T15:41:53.220774-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-20962","iteration":4} +{"schema_version":1,"timestamp":"2026-03-07T15:46:28.774669-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-20962","iteration":4,"success":true,"duration":275552838208} +{"schema_version":1,"timestamp":"2026-03-07T15:46:28.850326-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-20962","iteration":4} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.204252-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-20962","iteration":4,"success":true,"duration":353916209} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.262226-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-20962","iteration":4} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.262237-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-20962","iteration":4,"success":true,"duration":5708} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.309132-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-20962","iteration":4} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.403204-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-20962","iteration":4,"success":true,"duration":94064750} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.403212-05:00","type":"bead.completed","bead_id":"gromit-20962","bead_title":"Expand remediation runner signature for worktree propagation","iteration":4,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.403244-05:00","type":"bead.started","bead_id":"gromit-61cbs","bead_title":"Add unit tests for phase filtering and scoping in prompt assembler","iteration":5} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.403247-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-61cbs","iteration":5} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.548508-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-61cbs","iteration":5,"success":true,"duration":145260542} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.548515-05:00","type":"bead.completed","bead_id":"gromit-61cbs","bead_title":"Add unit tests for phase filtering and scoping in prompt assembler","iteration":5,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.548533-05:00","type":"bead.started","bead_id":"gromit-7a6rt","bead_title":"Implement topological sort for beads","iteration":6} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.548534-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-7a6rt","iteration":6} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.618854-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-7a6rt","iteration":6,"success":true,"duration":70319125} +{"schema_version":1,"timestamp":"2026-03-07T15:46:29.618862-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-7a6rt","iteration":6} +{"schema_version":1,"timestamp":"2026-03-07T15:49:49.764385-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-7a6rt","iteration":6,"success":true,"duration":200144753750} +{"schema_version":1,"timestamp":"2026-03-07T15:49:49.846022-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-7a6rt","iteration":6} +{"schema_version":1,"timestamp":"2026-03-07T15:49:49.886532-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-7a6rt","iteration":6,"success":true,"duration":40499625} +{"schema_version":1,"timestamp":"2026-03-07T15:49:49.934313-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-7a6rt","iteration":6} +{"schema_version":1,"timestamp":"2026-03-07T15:49:49.934326-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-7a6rt","iteration":6,"success":true,"duration":1542} +{"schema_version":1,"timestamp":"2026-03-07T15:49:49.981691-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-7a6rt","iteration":6} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.337598-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-7a6rt","iteration":6,"success":true,"duration":355892459} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.337643-05:00","type":"bead.completed","bead_id":"gromit-7a6rt","bead_title":"Implement topological sort for beads","iteration":6,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.337705-05:00","type":"bead.started","bead_id":"gromit-8yarv","bead_title":"Wire budget shaping into Assemble workflow","iteration":7} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.337708-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-8yarv","iteration":7} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.500314-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-8yarv","iteration":7,"success":true,"duration":162603292} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.50032-05:00","type":"bead.completed","bead_id":"gromit-8yarv","bead_title":"Wire budget shaping into Assemble workflow","iteration":7,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.500346-05:00","type":"bead.started","bead_id":"gromit-9z0j1","bead_title":"Implement budget shaping with scope adjustment in PromptAssembler","iteration":8} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.500347-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-9z0j1","iteration":8} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.641639-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-9z0j1","iteration":8,"success":true,"duration":141290792} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.641648-05:00","type":"bead.completed","bead_id":"gromit-9z0j1","bead_title":"Implement budget shaping with scope adjustment in PromptAssembler","iteration":8,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.641661-05:00","type":"bead.started","bead_id":"gromit-atr10","bead_title":"Wire Router into spec loop for plan/decompose/accept phases","iteration":9} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.641662-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-atr10","iteration":9} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.787496-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-atr10","iteration":9,"success":true,"duration":145832917} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.787502-05:00","type":"bead.completed","bead_id":"gromit-atr10","bead_title":"Wire Router into spec loop for plan/decompose/accept phases","iteration":9,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.787523-05:00","type":"bead.started","bead_id":"gromit-cufx0","bead_title":"Add integration tests for resume with gap analysis and revalidation","iteration":10} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.787524-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-cufx0","iteration":10} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.997225-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-cufx0","iteration":10,"success":true,"duration":209699959} +{"schema_version":1,"timestamp":"2026-03-07T15:49:50.997233-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-cufx0","iteration":10} +{"schema_version":1,"timestamp":"2026-03-07T16:04:06.775614-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-cufx0","iteration":10,"success":true,"duration":855775087625} +{"schema_version":1,"timestamp":"2026-03-07T16:04:06.849524-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-cufx0","iteration":10} +{"schema_version":1,"timestamp":"2026-03-07T16:04:06.889062-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-cufx0","iteration":10,"success":true,"duration":39529375} +{"schema_version":1,"timestamp":"2026-03-07T16:04:06.937184-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-cufx0","iteration":10} +{"schema_version":1,"timestamp":"2026-03-07T16:04:06.937206-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-cufx0","iteration":10,"success":true,"duration":13334} +{"schema_version":1,"timestamp":"2026-03-07T16:04:06.98495-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-cufx0","iteration":10} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.131506-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-cufx0","iteration":10,"success":true,"duration":146545875} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.131522-05:00","type":"bead.completed","bead_id":"gromit-cufx0","bead_title":"Add integration tests for resume with gap analysis and revalidation","iteration":10,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.131574-05:00","type":"bead.started","bead_id":"gromit-d2rpx","bead_title":"Instantiate and wire Router in run2 command","iteration":11} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.131578-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-d2rpx","iteration":11} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.2793-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-d2rpx","iteration":11,"success":true,"duration":147720292} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.279308-05:00","type":"bead.completed","bead_id":"gromit-d2rpx","bead_title":"Instantiate and wire Router in run2 command","iteration":11,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.279323-05:00","type":"bead.started","bead_id":"gromit-d4134","bead_title":"Add integration tests for bead loop routing and escalation","iteration":12} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.279324-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-d4134","iteration":12} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.423585-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-d4134","iteration":12,"success":true,"duration":144260083} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.42359-05:00","type":"bead.completed","bead_id":"gromit-d4134","bead_title":"Add integration tests for bead loop routing and escalation","iteration":12,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.423608-05:00","type":"bead.started","bead_id":"gromit-ew1l8","bead_title":"Implement Router ratio balancing and phase preferences logic","iteration":13} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.423608-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-ew1l8","iteration":13} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.569431-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-ew1l8","iteration":13,"success":true,"duration":145821958} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.569434-05:00","type":"bead.completed","bead_id":"gromit-ew1l8","bead_title":"Implement Router ratio balancing and phase preferences logic","iteration":13,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.569444-05:00","type":"bead.started","bead_id":"gromit-f4ouz","bead_title":"Add scoped CLAUDE.md loading to PromptAssembler","iteration":14} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.569445-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-f4ouz","iteration":14} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.641385-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-f4ouz","iteration":14,"success":true,"duration":71936792} +{"schema_version":1,"timestamp":"2026-03-07T16:04:07.641393-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-f4ouz","iteration":14} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.426401-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-f4ouz","iteration":14,"success":true,"duration":335783719542} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.499059-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-f4ouz","iteration":14} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.538724-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-f4ouz","iteration":14,"success":true,"duration":39650083} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.586665-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-f4ouz","iteration":14} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.586676-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-f4ouz","iteration":14,"success":true,"duration":4375} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.633943-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-f4ouz","iteration":14} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.790245-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-f4ouz","iteration":14,"success":true,"duration":156293250} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.790257-05:00","type":"bead.completed","bead_id":"gromit-f4ouz","bead_title":"Add scoped CLAUDE.md loading to PromptAssembler","iteration":14,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.790285-05:00","type":"bead.started","bead_id":"gromit-fhp2q","bead_title":"Implement gate re-queue logic in bead loop","iteration":15} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.790287-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-fhp2q","iteration":15} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.86676-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-fhp2q","iteration":15,"success":true,"duration":76471875} +{"schema_version":1,"timestamp":"2026-03-07T16:09:43.866768-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-fhp2q","iteration":15} +{"schema_version":1,"timestamp":"2026-03-07T16:17:46.500635-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-fhp2q","iteration":15,"success":true,"duration":482632017375} +{"schema_version":1,"timestamp":"2026-03-07T16:17:46.577968-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-fhp2q","iteration":15} +{"schema_version":1,"timestamp":"2026-03-07T16:17:46.618675-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-fhp2q","iteration":15,"success":true,"duration":40694750} +{"schema_version":1,"timestamp":"2026-03-07T16:17:46.66731-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-fhp2q","iteration":15} +{"schema_version":1,"timestamp":"2026-03-07T16:17:46.667329-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-fhp2q","iteration":15,"success":true,"duration":11500} +{"schema_version":1,"timestamp":"2026-03-07T16:17:46.715576-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-fhp2q","iteration":15} +{"schema_version":1,"timestamp":"2026-03-07T16:17:48.204994-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-fhp2q","iteration":15,"success":true,"duration":1489403416} +{"schema_version":1,"timestamp":"2026-03-07T16:17:48.205025-05:00","type":"bead.completed","bead_id":"gromit-fhp2q","bead_title":"Implement gate re-queue logic in bead loop","iteration":15,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:17:48.205098-05:00","type":"bead.started","bead_id":"gromit-jv6ns","bead_title":"Create Router interface and stub in v2/routing","iteration":16} +{"schema_version":1,"timestamp":"2026-03-07T16:17:48.205102-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-jv6ns","iteration":16} +{"schema_version":1,"timestamp":"2026-03-07T16:17:48.32269-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-jv6ns","iteration":16,"success":true,"duration":117586292} +{"schema_version":1,"timestamp":"2026-03-07T16:17:48.322698-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-jv6ns","iteration":16} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.158349-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-jv6ns","iteration":16,"success":true,"duration":341834340959} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.226995-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-jv6ns","iteration":16} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.266801-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-jv6ns","iteration":16,"success":true,"duration":39797250} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.314517-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-jv6ns","iteration":16} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.314526-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-jv6ns","iteration":16,"success":true,"duration":2208} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.361882-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-jv6ns","iteration":16} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.513717-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-jv6ns","iteration":16,"success":true,"duration":151828917} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.51373-05:00","type":"bead.completed","bead_id":"gromit-jv6ns","bead_title":"Create Router interface and stub in v2/routing","iteration":16,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.513753-05:00","type":"bead.started","bead_id":"gromit-jzbe3","bead_title":"Implement tier resolution in internal/v2/routing/tier.go","iteration":17} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.513756-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-jzbe3","iteration":17} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.590854-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-jzbe3","iteration":17,"success":true,"duration":77095916} +{"schema_version":1,"timestamp":"2026-03-07T16:23:30.590861-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-jzbe3","iteration":17} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.084705-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-jzbe3","iteration":17,"success":true,"duration":195493094042} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.157044-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-jzbe3","iteration":17} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.196812-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-jzbe3","iteration":17,"success":true,"duration":39758792} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.24439-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-jzbe3","iteration":17} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.244407-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-jzbe3","iteration":17,"success":true,"duration":1708} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.295706-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-jzbe3","iteration":17} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.39325-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-jzbe3","iteration":17,"success":true,"duration":97535750} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.393256-05:00","type":"bead.completed","bead_id":"gromit-jzbe3","bead_title":"Implement tier resolution in internal/v2/routing/tier.go","iteration":17,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.393276-05:00","type":"bead.started","bead_id":"gromit-u4837","bead_title":"Add integration tests for full prompt shaping pipeline","iteration":18} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.393277-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-u4837","iteration":18} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.534304-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-u4837","iteration":18,"success":true,"duration":141026250} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.53431-05:00","type":"bead.completed","bead_id":"gromit-u4837","bead_title":"Add integration tests for full prompt shaping pipeline","iteration":18,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.534322-05:00","type":"bead.started","bead_id":"gromit-uulh1","bead_title":"Add unit tests for tier resolution","iteration":19} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.534323-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-uulh1","iteration":19} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.676457-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-uulh1","iteration":19,"success":true,"duration":142132958} +{"schema_version":1,"timestamp":"2026-03-07T16:26:46.676465-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-uulh1","iteration":19} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.488873-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-uulh1","iteration":19,"success":true,"duration":125811924125} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.568245-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-uulh1","iteration":19} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.608368-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-uulh1","iteration":19,"success":true,"duration":40113709} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.656542-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-uulh1","iteration":19} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.656556-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-uulh1","iteration":19,"success":true,"duration":2667} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.703754-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-uulh1","iteration":19} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.86201-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-uulh1","iteration":19,"success":true,"duration":158246917} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.862025-05:00","type":"bead.completed","bead_id":"gromit-uulh1","bead_title":"Add unit tests for tier resolution","iteration":19,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.862049-05:00","type":"bead.started","bead_id":"gromit-v1j2u","bead_title":"Add phase filtering to rules loading in PromptAssembler","iteration":20} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.862051-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-v1j2u","iteration":20} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.939682-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-v1j2u","iteration":20,"success":true,"duration":77630125} +{"schema_version":1,"timestamp":"2026-03-07T16:28:52.939691-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-v1j2u","iteration":20} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.372451-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-v1j2u","iteration":20,"success":true,"duration":216431930875} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.441443-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-v1j2u","iteration":20} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.481473-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-v1j2u","iteration":20,"success":true,"duration":40018375} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.530042-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-v1j2u","iteration":20} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.530052-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-v1j2u","iteration":20,"success":true,"duration":2542} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.577196-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-v1j2u","iteration":20} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.67267-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-v1j2u","iteration":20,"success":true,"duration":95465417} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.672676-05:00","type":"bead.completed","bead_id":"gromit-v1j2u","bead_title":"Add phase filtering to rules loading in PromptAssembler","iteration":20,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.67269-05:00","type":"bead.started","bead_id":"gromit-v46qz","bead_title":"Add unit tests for topological sort","iteration":21} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.672691-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-v46qz","iteration":21} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.817001-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-v46qz","iteration":21,"success":true,"duration":144308417} +{"schema_version":1,"timestamp":"2026-03-07T16:32:29.817012-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-v46qz","iteration":21} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.519006-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-v46qz","iteration":21,"success":true,"duration":269700960750} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.587259-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-v46qz","iteration":21} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.627255-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-v46qz","iteration":21,"success":true,"duration":39986667} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.675529-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-v46qz","iteration":21} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.675547-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-v46qz","iteration":21,"success":true,"duration":7250} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.723145-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-v46qz","iteration":21} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.879055-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-v46qz","iteration":21,"success":true,"duration":155903125} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.879072-05:00","type":"bead.completed","bead_id":"gromit-v46qz","bead_title":"Add unit tests for topological sort","iteration":21,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.879095-05:00","type":"bead.started","bead_id":"gromit-w1200","bead_title":"Wire Router into bead loop routing seam","iteration":22} +{"schema_version":1,"timestamp":"2026-03-07T16:36:59.879096-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-w1200","iteration":22} +{"schema_version":1,"timestamp":"2026-03-07T16:37:00.009157-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-w1200","iteration":22,"success":true,"duration":130058583} +{"schema_version":1,"timestamp":"2026-03-07T16:37:00.009166-05:00","type":"bead.completed","bead_id":"gromit-w1200","bead_title":"Wire Router into bead loop routing seam","iteration":22,"success":false} +{"schema_version":1,"timestamp":"2026-03-07T16:37:00.009183-05:00","type":"bead.started","bead_id":"gromit-w83pd","bead_title":"Implement gap analysis for resume","iteration":23} +{"schema_version":1,"timestamp":"2026-03-07T16:37:00.009184-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-w83pd","iteration":23} +{"schema_version":1,"timestamp":"2026-03-07T16:37:00.066977-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-w83pd","iteration":23,"success":true,"duration":57793000} +{"schema_version":1,"timestamp":"2026-03-07T16:37:00.066987-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-w83pd","iteration":23} +{"schema_version":1,"timestamp":"2026-03-07T16:40:03.694684-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-w83pd","iteration":23,"success":true,"duration":183626276584} +{"schema_version":1,"timestamp":"2026-03-07T16:40:03.771632-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-w83pd","iteration":23} +{"schema_version":1,"timestamp":"2026-03-07T16:40:03.812267-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-w83pd","iteration":23,"success":true,"duration":40627875} +{"schema_version":1,"timestamp":"2026-03-07T16:40:03.861359-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-w83pd","iteration":23} +{"schema_version":1,"timestamp":"2026-03-07T16:40:03.861382-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-w83pd","iteration":23,"success":true,"duration":10833} +{"schema_version":1,"timestamp":"2026-03-07T16:40:03.909464-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-w83pd","iteration":23} +{"schema_version":1,"timestamp":"2026-03-07T16:40:04.005688-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-w83pd","iteration":23,"success":true,"duration":96209917} +{"schema_version":1,"timestamp":"2026-03-07T16:40:04.0057-05:00","type":"bead.completed","bead_id":"gromit-w83pd","bead_title":"Implement gap analysis for resume","iteration":23,"success":true} +{"schema_version":1,"timestamp":"2026-03-07T16:40:04.005713-05:00","type":"bead.started","bead_id":"gromit-zrrn7","bead_title":"Implement selective revalidation in spec loop","iteration":24} +{"schema_version":1,"timestamp":"2026-03-07T16:40:04.005723-05:00","type":"stage.started","stage_name":"gate:default","bead_id":"gromit-zrrn7","iteration":24} +{"schema_version":1,"timestamp":"2026-03-07T16:40:04.074518-05:00","type":"stage.completed","stage_name":"gate:default","bead_id":"gromit-zrrn7","iteration":24,"success":true,"duration":68794166} +{"schema_version":1,"timestamp":"2026-03-07T16:40:04.074526-05:00","type":"stage.started","stage_name":"build:default","bead_id":"gromit-zrrn7","iteration":24} +{"schema_version":1,"timestamp":"2026-03-07T16:45:12.659648-05:00","type":"stage.completed","stage_name":"build:default","bead_id":"gromit-zrrn7","iteration":24,"success":true,"duration":308583850375} +{"schema_version":1,"timestamp":"2026-03-07T16:45:12.730416-05:00","type":"stage.started","stage_name":"validate:default","bead_id":"gromit-zrrn7","iteration":24} +{"schema_version":1,"timestamp":"2026-03-07T16:45:12.770575-05:00","type":"stage.completed","stage_name":"validate:default","bead_id":"gromit-zrrn7","iteration":24,"success":true,"duration":40143709} +{"schema_version":1,"timestamp":"2026-03-07T16:45:12.818794-05:00","type":"stage.started","stage_name":"review:default","bead_id":"gromit-zrrn7","iteration":24} +{"schema_version":1,"timestamp":"2026-03-07T16:45:12.818811-05:00","type":"stage.completed","stage_name":"review:default","bead_id":"gromit-zrrn7","iteration":24,"success":true,"duration":10416} +{"schema_version":1,"timestamp":"2026-03-07T16:45:12.866532-05:00","type":"stage.started","stage_name":"epilogue:default","bead_id":"gromit-zrrn7","iteration":24} +{"schema_version":1,"timestamp":"2026-03-07T16:45:13.022158-05:00","type":"stage.completed","stage_name":"epilogue:default","bead_id":"gromit-zrrn7","iteration":24,"success":true,"duration":155617458} +{"schema_version":1,"timestamp":"2026-03-07T16:45:13.022172-05:00","type":"bead.completed","bead_id":"gromit-zrrn7","bead_title":"Implement selective revalidation in spec loop","iteration":24,"success":true} diff --git a/.gromit/v2/plan.md b/.gromit/v2/plan.md new file mode 100644 index 000000000..3cbe5d1b3 --- /dev/null +++ b/.gromit/v2/plan.md @@ -0,0 +1 @@ +All three exploration agents have completed. The plan is finalized at `.gromit/v2/plan.md`. \ No newline at end of file diff --git a/cmd/gromit/run2.go b/cmd/gromit/run2.go index a161c6a3e..8f55b9be0 100644 --- a/cmd/gromit/run2.go +++ b/cmd/gromit/run2.go @@ -11,6 +11,7 @@ import ( "strings" "sync" "syscall" + "time" "github.com/danabrams/gromit/internal/bead" "github.com/danabrams/gromit/internal/config" @@ -23,7 +24,9 @@ import ( "github.com/danabrams/gromit/internal/v2/adapter/presenter" "github.com/danabrams/gromit/internal/v2/adapter/tasktracker" "github.com/danabrams/gromit/internal/v2/dep" + "github.com/danabrams/gromit/internal/v2/llmtypes" "github.com/danabrams/gromit/internal/v2/loop" + "github.com/danabrams/gromit/internal/v2/routing" v2spec "github.com/danabrams/gromit/internal/v2/spec" "github.com/spf13/cobra" ) @@ -138,6 +141,8 @@ func run2(cmd *cobra.Command, args []string) error { wg.Wait() }() + router, phaseModels := buildRouter(cfg) + baseOpts := []loop.SpecLoopOption{ newSpecLoopEmitterFn(emitter), loop.WithPlanStage(components.PlanStage), @@ -149,6 +154,12 @@ func run2(cmd *cobra.Command, args []string) error { loop.WithStageCommitter(components.StageCommitter), loop.WithTypedEmitter(components.TypedEmitter), } + if router != nil { + baseOpts = append(baseOpts, loop.WithRouter(router)) + } + if len(phaseModels) > 0 { + baseOpts = append(baseOpts, loop.WithPhaseModels(phaseModels)) + } for _, specFile := range specFiles { if err := specFile.CheckDependencies(specsDir); err != nil { return fmt.Errorf("checking dependencies for %s: %w", specFile.ID, err) @@ -258,6 +269,80 @@ func loadSpecFromArg(arg string) (*v2spec.Spec, error) { return specFile, nil } +// buildRouter constructs a Router from the config's provider and routing +// settings. Returns (nil, nil) when no providers are configured, preserving +// backward compatibility — the spec loop handles a nil Router gracefully. +func buildRouter(cfg *config.Config) (*routing.Router, map[string]string) { + if cfg == nil || len(cfg.Providers) == 0 { + return nil, nil + } + + binary := "claude" + if strings.TrimSpace(cfg.Claude.Binary) != "" { + binary = strings.TrimSpace(cfg.Claude.Binary) + } + flags := []string{} + if len(cfg.Claude.Flags) > 0 { + flags = append(flags, cfg.Claude.Flags...) + } + timeout := 15 * time.Minute + if cfg.Claude.Timeout > 0 { + timeout = time.Duration(cfg.Claude.Timeout) * time.Second + } + + providers := make(map[string]llmtypes.LLMProvider, len(cfg.Providers)) + for name, def := range cfg.Providers { + provBinary := binary + if strings.TrimSpace(def.Binary) != "" { + provBinary = strings.TrimSpace(def.Binary) + } + provFlags := flags + if len(def.Flags) > 0 { + provFlags = append([]string(nil), def.Flags...) + } + providers[name] = llm.NewClaudeAdapter(provBinary, provFlags, timeout) + } + + var cooldown time.Duration + if cd := strings.TrimSpace(cfg.Routing.Fallback.Cooldown); cd != "" { + if parsed, err := time.ParseDuration(cd); err == nil { + cooldown = parsed + } + } + + router := routing.NewRouter(routing.RouterConfig{ + Providers: providers, + PhasePreferences: cfg.Routing.PhasePreferences, + Ratio: cfg.Routing.Ratio, + Cooldown: cooldown, + }) + + phaseModels := phaseModelsFromConfig(cfg.Methodology.PhaseModels) + return router, phaseModels +} + +// phaseModelsFromConfig converts the structured PhaseModelsConfig into a +// flat map[string]string suitable for routing.TierForPhase. +func phaseModelsFromConfig(pm config.PhaseModelsConfig) map[string]string { + m := map[string]string{} + if pm.Decompose != "" { + m["decompose"] = pm.Decompose + } + if pm.Build != "" { + m["build"] = pm.Build + } + if pm.Red != "" { + m["red"] = pm.Red + } + if pm.Green != "" { + m["green"] = pm.Green + } + if pm.Refactor != "" { + m["refactor"] = pm.Refactor + } + return m +} + func startRun2Subscribers(ctx context.Context, emitter *events.Emitter, output io.Writer, logsDir string) (*sync.WaitGroup, error) { if emitter == nil { return nil, fmt.Errorf("emitter is nil") diff --git a/internal/v2/acceptance_isolation_test.go b/internal/v2/acceptance_isolation_test.go index 09db54100..68632a3a8 100644 --- a/internal/v2/acceptance_isolation_test.go +++ b/internal/v2/acceptance_isolation_test.go @@ -54,7 +54,7 @@ func TestPromptAssemblerCompilesAllLayers(t *testing.T) { t.Parallel() assembler := prompt.NewPromptAssembler("base", "project", "instance", "fragment") - output := assembler.Assemble() + output := assembler.Assemble("", prompt.BeadInfo{}) order := []string{"base", "project", "instance", "fragment"} for idx, fragment := range order { diff --git a/internal/v2/loop/bead_loop.go b/internal/v2/loop/bead_loop.go index ecd10c70b..18518881d 100644 --- a/internal/v2/loop/bead_loop.go +++ b/internal/v2/loop/bead_loop.go @@ -16,6 +16,7 @@ import ( "github.com/danabrams/gromit/internal/v2/event" "github.com/danabrams/gromit/internal/v2/generation" v2review "github.com/danabrams/gromit/internal/v2/review" + "github.com/danabrams/gromit/internal/v2/routing" "github.com/danabrams/gromit/internal/v2/stage" reviewstage "github.com/danabrams/gromit/internal/v2/stage/review" "github.com/danabrams/gromit/internal/v2/stage/triage" @@ -48,6 +49,8 @@ type BeadLoopConfig struct { StartGeneration int Git GitCommitter StageCommitter StageCommitter + Router *routing.Router // optional: if nil, routing is skipped + PhaseModels map[string]string // optional: phase -> starting tier } // BeadLoopResult captures metadata produced by a bead loop execution. @@ -72,6 +75,8 @@ type BeadLoop struct { outOfScopeFindings []v2review.Finding git GitCommitter stageCommitter StageCommitter + router *routing.Router + phaseModels map[string]string // run-scoped state for in-loop decomposition resolver *dep.Resolver beadMap map[string]*bead.Bead @@ -128,6 +133,8 @@ func NewBeadLoop(config BeadLoopConfig) (*BeadLoop, error) { startGeneration: config.StartGeneration, git: config.Git, stageCommitter: config.StageCommitter, + router: config.Router, + phaseModels: config.PhaseModels, } if config.Emitter != nil && config.LegacyEmitter != nil { event.BridgeTypedToLegacy(config.Emitter, config.LegacyEmitter) @@ -157,6 +164,16 @@ func (b *BeadLoop) Run(ctx context.Context, beads []*bead.Bead, stopCh <-chan st } } + // Pre-sort beads topologically so the resolver's add-order reflects + // dependency ordering. TopologicalSort provides the initial execution + // order; dep.Resolver handles dynamic re-ordering during execution + // (blocking/unblocking as beads complete). + sorted, err := TopologicalSort(beads) + if err != nil { + return BeadLoopResult{}, fmt.Errorf("topological pre-sort: %w", err) + } + beads = sorted + b.resolver = dep.NewResolver() b.beadMap = make(map[string]*bead.Bead, len(beads)) b.completed = nil @@ -173,6 +190,13 @@ func (b *BeadLoop) Run(ctx context.Context, beads []*bead.Bead, stopCh <-chan st b.resolver.Add(id, collectDependencies(beadItem)) } + // blockedThisPass tracks bead IDs blocked by the gate in the current pass. + // These are passed to the resolver as "effective completed" so it can + // return their dependents, but they are NOT in b.completed and will be + // retried at the start of the next pass if any progress was made. + var blockedThisPass []string + passProgress := 0 + iteration := 1 runLoop: for { @@ -190,12 +214,34 @@ runLoop: default: } } - next, err := b.resolver.Next(b.completed) + + // Build the effective completed set: real completed + blocked-this-pass + // so the resolver skips blocked beads and returns their dependents. + effectiveCompleted := b.completed + if len(blockedThisPass) > 0 { + effectiveCompleted = make([]string, 0, len(b.completed)+len(blockedThisPass)) + effectiveCompleted = append(effectiveCompleted, b.completed...) + effectiveCompleted = append(effectiveCompleted, blockedThisPass...) + } + + next, err := b.resolver.Next(effectiveCompleted) if err != nil { return BeadLoopResult{}, fmt.Errorf("resolve bead: %w", err) } if next == "" { - break + if len(blockedThisPass) == 0 { + // Normal completion — no blocked beads pending. + break + } + if passProgress == 0 { + // Full pass with zero progress: all remaining beads are blocked. + blockedIDs := strings.Join(blockedThisPass, ", ") + return BeadLoopResult{}, fmt.Errorf("bead loop: all remaining beads blocked with no progress: %s", blockedIDs) + } + // Progress was made this pass — retry the blocked beads. + blockedThisPass = nil + passProgress = 0 + continue } beadItem, ok := b.beadMap[next] if !ok { @@ -211,15 +257,16 @@ runLoop: // Gate skipped this bead — mark completed and continue to next b.emitBeadCompleted(beadItem, iteration, true, 0) b.completed = append(b.completed, next) + passProgress++ iteration++ continue } if errors.Is(err, errBeadBlocked) { - // Gate blocked this bead — mark completed so the resolver - // moves on. The bead is deferred to a future run, not - // retried within this loop invocation. - b.emitBeadCompleted(beadItem, iteration, false, 0) - b.completed = append(b.completed, next) + // Gate blocked this bead — re-queue to end of current pass. + // Do NOT mark completed; the bead will be retried after other + // beads in this pass make progress. If no progress is made + // after a full pass, the loop stops with an error. + blockedThisPass = append(blockedThisPass, next) iteration++ continue } @@ -229,6 +276,7 @@ runLoop: if errors.Is(err, errBeadFailed) { log.Printf("bead %s failed (continuing loop): %v", next, err) b.completed = append(b.completed, next) + passProgress++ iteration++ continue } @@ -238,6 +286,7 @@ runLoop: } b.emitBeadCompleted(beadItem, iteration, true, 0) b.completed = append(b.completed, next) + passProgress++ iteration++ } return BeadLoopResult{ @@ -365,6 +414,12 @@ func (b *BeadLoop) runStageEntry(ctx context.Context, beadItem *bead.Bead, itera return nil } + // Determine starting tier for this stage's phase via routing. + startTier := routing.TierMedium + if b.router != nil { + startTier = routing.TierForPhase(stageName, b.phaseModels, routing.TierMedium) + } + retriesRemaining := entry.retryConfig.MaxRetries if retriesRemaining < 0 { retriesRemaining = 0 @@ -375,6 +430,21 @@ func (b *BeadLoop) runStageEntry(ctx context.Context, beadItem *bead.Bead, itera for { req := b.stageRequest(beadItem, iteration, stageRetryContext(attempt, priorFailures)) + + // Apply routing: select provider and model for this stage. + if b.router != nil { + escalationLevel := 0 + if req.RetryContext != nil { + escalationLevel = req.RetryContext.EscalationLevel + } + tier := routing.EscalationTier(startTier, escalationLevel) + provider, model, routeErr := b.router.Select(stageName, tier) + if routeErr == nil && provider != nil { + req.Provider = provider + req.Model = model + } + } + b.emitStageStarted(stageName, beadItem.ID, iteration) start := time.Now() res, err := b.runStage(ctx, entry.stage, req) @@ -477,7 +547,14 @@ func stageRetryContext(attempt int, priorFailures []string) *stage.RetryContext if attempt <= 1 && len(priorFailures) == 0 { return nil } - ctx := &stage.RetryContext{Attempt: attempt} + escalation := 0 + if attempt > 1 { + escalation = attempt - 1 + } + ctx := &stage.RetryContext{ + Attempt: attempt, + EscalationLevel: escalation, + } if len(priorFailures) > 0 { ctx.PriorFailures = append([]string(nil), priorFailures...) } diff --git a/internal/v2/loop/bead_loop_routing_test.go b/internal/v2/loop/bead_loop_routing_test.go new file mode 100644 index 000000000..d1a870815 --- /dev/null +++ b/internal/v2/loop/bead_loop_routing_test.go @@ -0,0 +1,172 @@ +package loop + +import ( + "context" + "testing" + "time" + + "github.com/danabrams/gromit/internal/bead" + "github.com/danabrams/gromit/internal/v2/llmtypes" + "github.com/danabrams/gromit/internal/v2/routing" + "github.com/danabrams/gromit/internal/v2/stage" +) + +// spyStage captures Request fields from each invocation. +type spyStage struct { + name string + models []string + hadProv []bool + decision stage.Decision + failN int // fail the first N calls + calls int +} + +func (s *spyStage) Name() string { return s.name } + +func (s *spyStage) RetryConfig() stage.RetryConfig { + return stage.RetryConfig{MaxRetries: 3} +} + +func (s *spyStage) Run(_ context.Context, req *stage.Request) (*stage.Result, error) { + s.models = append(s.models, req.Model) + s.hadProv = append(s.hadProv, req.Provider != nil) + s.calls++ + if s.failN > 0 && s.calls <= s.failN { + return &stage.Result{Decision: stage.DecisionFail}, nil + } + return &stage.Result{Decision: s.decision}, nil +} + +// fakeProvider satisfies llmtypes.LLMProvider for testing. +type fakeProvider struct{} + +func (f *fakeProvider) Invoke(_ context.Context, _ llmtypes.LLMInvokeRequest) (*llmtypes.LLMInvokeResponse, error) { + return &llmtypes.LLMInvokeResponse{}, nil +} +func (f *fakeProvider) StreamInvoke(_ context.Context, _ llmtypes.LLMStreamInvokeRequest) (*llmtypes.LLMInvokeResponse, error) { + return &llmtypes.LLMInvokeResponse{}, nil +} + +func TestBeadLoopRouterPopulatesProviderAndModel(t *testing.T) { + t.Parallel() + + buildSpy := &spyStage{name: "build", decision: stage.DecisionProceed} + + r := routing.NewRouter(routing.RouterConfig{ + Providers: map[string]llmtypes.LLMProvider{ + "claude": &fakeProvider{}, + }, + Ratio: map[string]int{"claude": 1}, + Cooldown: time.Minute, + }) + + cfg := BeadLoopConfig{ + Gate: newNoopStage("gate"), + Build: buildSpy, + Validate: newNoopStage("validate"), + Review: newNoopStage("review"), + Epilogue: newNoopStage("epilogue"), + Router: r, + PhaseModels: map[string]string{"build": "low"}, + } + + loop, err := NewBeadLoop(cfg) + if err != nil { + t.Fatalf("NewBeadLoop: %v", err) + } + + beads := []*bead.Bead{{ID: "bead-1"}} + if _, err := loop.Run(context.Background(), beads, nil); err != nil { + t.Fatalf("Run failed: %v", err) + } + + if len(buildSpy.models) == 0 { + t.Fatal("build stage was never called") + } + if buildSpy.models[0] != "low" { + t.Fatalf("build model = %q, want %q", buildSpy.models[0], "low") + } + if !buildSpy.hadProv[0] { + t.Fatal("build Provider was nil, want non-nil") + } +} + +func TestBeadLoopEscalationBumpsTierOnRetry(t *testing.T) { + t.Parallel() + + // Build fails once then succeeds — should see tier escalation. + buildSpy := &spyStage{name: "build", decision: stage.DecisionProceed, failN: 1} + + r := routing.NewRouter(routing.RouterConfig{ + Providers: map[string]llmtypes.LLMProvider{ + "claude": &fakeProvider{}, + }, + Ratio: map[string]int{"claude": 1}, + Cooldown: time.Minute, + }) + + cfg := BeadLoopConfig{ + Gate: newNoopStage("gate"), + Build: buildSpy, + Validate: newNoopStage("validate"), + Review: newNoopStage("review"), + Epilogue: newNoopStage("epilogue"), + Router: r, + PhaseModels: map[string]string{"build": "low"}, + } + + loop, err := NewBeadLoop(cfg) + if err != nil { + t.Fatalf("NewBeadLoop: %v", err) + } + + beads := []*bead.Bead{{ID: "bead-1"}} + if _, err := loop.Run(context.Background(), beads, nil); err != nil { + t.Fatalf("Run failed: %v", err) + } + + if len(buildSpy.models) < 2 { + t.Fatalf("expected at least 2 build calls, got %d", len(buildSpy.models)) + } + if buildSpy.models[0] != "low" { + t.Fatalf("first attempt model = %q, want %q", buildSpy.models[0], "low") + } + if buildSpy.models[1] != "medium" { + t.Fatalf("second attempt model = %q, want %q", buildSpy.models[1], "medium") + } +} + +func TestBeadLoopRoutingSkippedWhenRouterNil(t *testing.T) { + t.Parallel() + + buildSpy := &spyStage{name: "build", decision: stage.DecisionProceed} + + cfg := BeadLoopConfig{ + Gate: newNoopStage("gate"), + Build: buildSpy, + Validate: newNoopStage("validate"), + Review: newNoopStage("review"), + Epilogue: newNoopStage("epilogue"), + // Router intentionally nil + } + + loop, err := NewBeadLoop(cfg) + if err != nil { + t.Fatalf("NewBeadLoop: %v", err) + } + + beads := []*bead.Bead{{ID: "bead-1"}} + if _, err := loop.Run(context.Background(), beads, nil); err != nil { + t.Fatalf("Run failed: %v", err) + } + + if len(buildSpy.models) == 0 { + t.Fatal("build stage was never called") + } + if buildSpy.models[0] != "" { + t.Fatalf("build model = %q, want empty (no router)", buildSpy.models[0]) + } + if buildSpy.hadProv[0] { + t.Fatal("build Provider should be nil when router is nil") + } +} diff --git a/internal/v2/loop/bead_loop_test.go b/internal/v2/loop/bead_loop_test.go index 9fe5da22b..1fe067fa8 100644 --- a/internal/v2/loop/bead_loop_test.go +++ b/internal/v2/loop/bead_loop_test.go @@ -1430,7 +1430,10 @@ func TestBeadLoopGateSkipContinuesLoop(t *testing.T) { func TestBeadLoopGateBlockDefersBeadAndContinues(t *testing.T) { t.Parallel() - // Gate that blocks the first bead, proceeds for the second + // Gate that blocks "block-me" on every call, proceeds for everything else. + // With re-queue logic: "run-me" makes progress in pass 1, "block-me" is + // re-queued, then retried in pass 2 where it still blocks with no + // progress → loop returns a descriptive error. gateStage := &scriptedGateStage{ name: "gate", decisions: map[string]stage.Decision{"block-me": stage.DecisionBlock}, @@ -1456,16 +1459,124 @@ func TestBeadLoopGateBlockDefersBeadAndContinues(t *testing.T) { {ID: "block-me"}, } _, err = loop.Run(context.Background(), beads, nil) - if err != nil { - t.Fatalf("Run should succeed, got: %v", err) + // "run-me" makes progress in pass 1; "block-me" is retried in pass 2 + // and still blocks → loop returns error for permanently blocked bead. + if err == nil { + t.Fatal("expected error when blocked bead cannot proceed after retry, got nil") + } + if !strings.Contains(err.Error(), "blocked") { + t.Fatalf("expected error to mention 'blocked', got: %v", err) } - // Build should only run for "run-me" + // Build should only run for "run-me" — never for the blocked bead if build.runCount != 1 { t.Fatalf("build run count = %d, want 1 (only run-me)", build.runCount) } } +// TestBeadLoopAllBeadsBlockedReturnsError verifies that when all beads are +// blocked by the gate in a full pass with no progress, the loop stops with +// a descriptive error instead of completing silently. +func TestBeadLoopAllBeadsBlockedReturnsError(t *testing.T) { + t.Parallel() + + gateStage := &scriptedGateStage{ + name: "gate", + decisions: map[string]stage.Decision{ + "bead-1": stage.DecisionBlock, + "bead-2": stage.DecisionBlock, + }, + } + + cfg := BeadLoopConfig{ + Gate: gateStage, + Build: newNoopStage("build"), + Validate: newNoopStage("validate"), + Review: newNoopStage("review"), + Epilogue: newNoopStage("epilogue"), + } + + loop, err := NewBeadLoop(cfg) + if err != nil { + t.Fatalf("NewBeadLoop: %v", err) + } + + beads := []*bead.Bead{ + {ID: "bead-1"}, + {ID: "bead-2"}, + } + + _, err = loop.Run(context.Background(), beads, nil) + if err == nil { + t.Fatal("expected error when all beads blocked, got nil") + } + if !strings.Contains(err.Error(), "blocked") { + t.Fatalf("expected error to mention 'blocked', got: %v", err) + } +} + +// TestBeadLoopBlockedBeadRetriedAfterProgress verifies that a bead blocked by +// the gate is retried in the next pass after another bead makes progress. +// If the previously-blocked bead proceeds on retry, the loop completes without error. +func TestBeadLoopBlockedBeadRetriedAfterProgress(t *testing.T) { + t.Parallel() + + // Gate that blocks "retry-me" on the first call, proceeds on subsequent calls. + callCounts := map[string]int{} + gateStage := &callCountingGateStage{ + name: "gate", + callCounts: callCounts, + // Block "retry-me" only on its first gate invocation + firstCallBlock: map[string]bool{"retry-me": true}, + } + + cfg := BeadLoopConfig{ + Gate: gateStage, + Build: newNoopStage("build"), + Validate: newNoopStage("validate"), + Review: newNoopStage("review"), + Epilogue: newNoopStage("epilogue"), + } + + loop, err := NewBeadLoop(cfg) + if err != nil { + t.Fatalf("NewBeadLoop: %v", err) + } + + beads := []*bead.Bead{ + {ID: "proceed-me"}, + {ID: "retry-me"}, + } + + _, err = loop.Run(context.Background(), beads, nil) + if err != nil { + t.Fatalf("expected loop to succeed after blocked bead unblocks on retry, got: %v", err) + } + + // Gate should have been called twice for "retry-me": once blocked, once proceeds + if callCounts["retry-me"] != 2 { + t.Fatalf("gate call count for retry-me = %d, want 2 (blocked then proceed)", callCounts["retry-me"]) + } +} + +// callCountingGateStage is a gate that blocks a bead on its first invocation +// and proceeds on all subsequent invocations. +type callCountingGateStage struct { + name string + callCounts map[string]int + firstCallBlock map[string]bool +} + +func (s *callCountingGateStage) Name() string { return s.name } + +func (s *callCountingGateStage) Run(_ context.Context, req *stage.Request) (*stage.Result, error) { + s.callCounts[req.Bead.ID]++ + if s.firstCallBlock[req.Bead.ID] && s.callCounts[req.Bead.ID] == 1 { + return &stage.Result{Decision: stage.DecisionBlock}, nil + } + return &stage.Result{Decision: stage.DecisionProceed}, nil +} + // scriptedGateStage returns configured decisions per bead ID; defaults to Proceed. type scriptedGateStage struct { name string diff --git a/internal/v2/loop/gap_analysis.go b/internal/v2/loop/gap_analysis.go new file mode 100644 index 000000000..c04d31b59 --- /dev/null +++ b/internal/v2/loop/gap_analysis.go @@ -0,0 +1,28 @@ +package loop + +import "github.com/danabrams/gromit/internal/bead" + +// FlagChangedBeads returns the subset of beads whose committed files overlap +// with the changed files. This is the core of resume gap analysis: given a +// file-level diff and a bead-to-file mapping from commit history, it identifies +// which completed beads need selective revalidation. +func FlagChangedBeads(beads []*bead.Bead, changedFiles []string, beadFiles map[string][]string) []*bead.Bead { + changed := make(map[string]bool, len(changedFiles)) + for _, f := range changedFiles { + changed[f] = true + } + + var flagged []*bead.Bead + for _, b := range beads { + if b == nil { + continue + } + for _, f := range beadFiles[b.ID] { + if changed[f] { + flagged = append(flagged, b) + break + } + } + } + return flagged +} diff --git a/internal/v2/loop/gap_analysis_test.go b/internal/v2/loop/gap_analysis_test.go new file mode 100644 index 000000000..3f16435f1 --- /dev/null +++ b/internal/v2/loop/gap_analysis_test.go @@ -0,0 +1,59 @@ +package loop + +import ( + "testing" + + "github.com/danabrams/gromit/internal/bead" +) + +func TestFlagChangedBeads_FlagsBeadsWithOverlappingFiles(t *testing.T) { + t.Parallel() + + beads := []*bead.Bead{ + {ID: "bead-a"}, + {ID: "bead-b"}, + {ID: "bead-c"}, + } + changedFiles := []string{"internal/foo/foo.go", "internal/bar/bar.go"} + beadFiles := map[string][]string{ + "bead-a": {"internal/foo/foo.go", "internal/foo/foo_test.go"}, + "bead-b": {"internal/qux/qux.go"}, + "bead-c": {"internal/bar/bar.go"}, + } + + flagged := FlagChangedBeads(beads, changedFiles, beadFiles) + + if len(flagged) != 2 { + t.Fatalf("got %d flagged beads, want 2", len(flagged)) + } + ids := make([]string, len(flagged)) + for i, b := range flagged { + ids[i] = b.ID + } + wantIDs := map[string]bool{"bead-a": true, "bead-c": true} + for _, id := range ids { + if !wantIDs[id] { + t.Errorf("unexpected flagged bead %q", id) + } + } +} + +func TestFlagChangedBeads_NoOverlapReturnsEmpty(t *testing.T) { + t.Parallel() + + beads := []*bead.Bead{ + {ID: "bead-x"}, + {ID: "bead-y"}, + } + changedFiles := []string{"internal/other/other.go"} + beadFiles := map[string][]string{ + "bead-x": {"internal/alpha/alpha.go"}, + "bead-y": {"internal/beta/beta.go"}, + } + + flagged := FlagChangedBeads(beads, changedFiles, beadFiles) + + if len(flagged) != 0 { + t.Fatalf("got %d flagged beads, want 0", len(flagged)) + } +} diff --git a/internal/v2/loop/integration_test.go b/internal/v2/loop/integration_test.go index fbbc0ee5c..3e418cbd4 100644 --- a/internal/v2/loop/integration_test.go +++ b/internal/v2/loop/integration_test.go @@ -6,12 +6,14 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/danabrams/gromit/internal/bead" "github.com/danabrams/gromit/internal/config" "github.com/danabrams/gromit/internal/events" "github.com/danabrams/gromit/internal/v2/adapter" "github.com/danabrams/gromit/internal/v2/adapter/llm" + "github.com/danabrams/gromit/internal/v2/adapter/tasktracker" "github.com/danabrams/gromit/internal/v2/generation" stagepkg "github.com/danabrams/gromit/internal/v2/stage" "github.com/danabrams/gromit/internal/v2/testutil" @@ -252,7 +254,7 @@ func defaultIntegrationBeadLoopConfig() BeadLoopConfig { } } -func (r *integrationRemediationRunner) Run(ctx context.Context, specID string) error { +func (r *integrationRemediationRunner) Run(ctx context.Context, specID, _ string) error { r.calls++ if r.generationCap >= 0 { r.emitGenerationCapEvents(specID) @@ -319,6 +321,7 @@ type integrationGitAdapter struct { *testutil.FakeGit t *testing.T gapAnalysisContent string + planContent string } func newIntegrationGitAdapter(t *testing.T) *integrationGitAdapter { @@ -334,16 +337,23 @@ func (g *integrationGitAdapter) Checkout(ctx context.Context, specID string) (st if err != nil { return "", err } - if g.gapAnalysisContent == "" { - return worktree, nil - } gromitPath := filepath.Join(worktree, ".gromit", "v2") - if err := os.MkdirAll(gromitPath, 0o755); err != nil { - g.t.Fatalf("create gromit dir: %v", err) + if g.gapAnalysisContent != "" || g.planContent != "" { + if err := os.MkdirAll(gromitPath, 0o755); err != nil { + g.t.Fatalf("create gromit dir: %v", err) + } + } + if g.gapAnalysisContent != "" { + path := filepath.Join(gromitPath, "gap-analysis.md") + if err := os.WriteFile(path, []byte(g.gapAnalysisContent), 0o644); err != nil { + g.t.Fatalf("write gap analysis: %v", err) + } } - path := filepath.Join(gromitPath, "gap-analysis.md") - if err := os.WriteFile(path, []byte(g.gapAnalysisContent), 0o644); err != nil { - g.t.Fatalf("write gap analysis: %v", err) + if g.planContent != "" { + path := filepath.Join(gromitPath, "plan.md") + if err := os.WriteFile(path, []byte(g.planContent), 0o644); err != nil { + g.t.Fatalf("write plan: %v", err) + } } return worktree, nil } @@ -403,3 +413,170 @@ func assertPresenterSuccess(t *testing.T, presenter *testutil.FakePresenter, wan t.Fatalf("presenter summary success = %v, want %v", got, want) } } + +// TestIntegration_ResumeWithGapAnalysisAndRevalidation exercises the resume path: +// existing beads are found via the task tracker, plan is loaded from disk, +// gap analysis identifies affected beads, and selective revalidation re-queues them. +func TestIntegration_ResumeWithGapAnalysisAndRevalidation(t *testing.T) { + t.Parallel() + + ctx := context.Background() + specID := "spec-resume-gap" + cfg := &config.Config{} + + emitter := events.NewEmitter() + ch := emitter.Subscribe() + t.Cleanup(func() { + emitter.Unsubscribe(ch) + }) + + git := newIntegrationGitAdapter(t) + git.planContent = "existing plan from prior run" + llmAdapter := newIntegrationLLMAdapter() + taskTracker := newIntegrationTaskTrackerAdapter() + presenter := newIntegrationPresenterAdapter(t) + + // Seed three beads with the spec label so queryExistingBeads finds them. + label := fmt.Sprintf("spec:%s", specID) + for _, title := range []string{"bead-a", "bead-b", "bead-c"} { + _, err := taskTracker.CreateBead(ctx, tasktracker.CreateBeadRequest{ + Title: title, + Labels: []string{label}, + }) + if err != nil { + t.Fatalf("seed bead %s: %v", title, err) + } + } + + // Gap analyzer: report that "main.go" changed and bead-1 (first seeded) touches it. + gap := &mockGapAnalyzer{ + changedFiles: []string{"main.go"}, + beadFileMap: map[string][]string{"bead-1": {"main.go", "util.go"}}, + } + + // Revalidator: return the flagged bead as needing re-queue. + revalidator := &mockRevalidator{} + + accept := newScriptedAcceptStage(stagepkg.Result{Decision: stagepkg.DecisionProceed}) + _, beadLoop := newIntegrationLoopComponents(t, specID) + planStage := newFakePlanStage(specID) + presentStage, summaryCtx := newPresentStageForTest(t, cfg, presenter) + + adapters := adapter.AdapterSet{ + Git: git, + LLM: llmAdapter, + TaskTracker: taskTracker, + Presenter: presenter, + } + + loopInstance, err := NewSpecLoop(adapters, cfg, noopDependencyGate{}, + WithEmitter(emitter), + WithPlanStage(planStage), + WithPresentStage(presentStage, summaryCtx), + WithAcceptStage(accept), + WithBeadLoop(beadLoop), + WithGapAnalyzer(gap), + WithSelectiveRevalidator(revalidator), + ) + if err != nil { + t.Fatalf("create spec loop: %v", err) + } + + if err := loopInstance.Run(ctx, specID, nil); err != nil { + t.Fatalf("run spec loop: %v", err) + } + + // Plan stage should NOT have been called (plan loaded from disk). + if planStage.called { + t.Fatal("plan stage was called, want skipped (should resume from disk)") + } + + // Gap analyzer should have been called exactly once. + if gap.calls != 1 { + t.Fatalf("gap analyzer calls = %d, want 1", gap.calls) + } + + // Revalidator should have been called exactly once. + if revalidator.calls != 1 { + t.Fatalf("revalidator calls = %d, want 1", revalidator.calls) + } + + // The revalidator received the flagged bead (bead-1 overlaps changed files). + if len(revalidator.receivedBeads) != 1 { + t.Fatalf("revalidator received %d beads, want 1", len(revalidator.receivedBeads)) + } + if revalidator.receivedBeads[0].ID != "bead-1" { + t.Fatalf("revalidator bead ID = %q, want %q", revalidator.receivedBeads[0].ID, "bead-1") + } + + // Accept should have been called (spec completed successfully). + if accept.calls != 1 { + t.Fatalf("accept calls = %d, want 1", accept.calls) + } + + // Should see DecomposeResumedEvent and PlanResumedEvent in the event stream. + evts := drainEvents(ch) + foundDecomposeResumed := false + foundPlanResumed := false + for _, evt := range evts { + switch evt.(type) { + case *events.DecomposeResumedEvent: + foundDecomposeResumed = true + case *events.PlanResumedEvent: + foundPlanResumed = true + } + } + if !foundPlanResumed { + types := make([]string, len(evts)) + for i, e := range evts { + types[i] = fmt.Sprintf("%T", e) + } + t.Fatalf("expected PlanResumedEvent in events, got: %v", types) + } + if !foundDecomposeResumed { + types := make([]string, len(evts)) + for i, e := range evts { + types[i] = fmt.Sprintf("%T", e) + } + t.Fatalf("expected DecomposeResumedEvent in events, got: %v", types) + } + + assertPresenterSuccess(t, presenter, true) +} + +func drainEvents(ch chan events.Event) []events.Event { + var evts []events.Event + deadline := time.After(time.Second) + for { + select { + case evt := <-ch: + evts = append(evts, evt) + case <-deadline: + return evts + } + } +} + +// mockGapAnalyzer implements GapAnalyzer for testing. +type mockGapAnalyzer struct { + changedFiles []string + beadFileMap map[string][]string + calls int +} + +func (m *mockGapAnalyzer) Analyze(_ context.Context, _ string, _ []*bead.Bead) ([]string, map[string][]string, error) { + m.calls++ + return m.changedFiles, m.beadFileMap, nil +} + +// mockRevalidator implements SelectiveRevalidator for testing. +type mockRevalidator struct { + calls int + receivedBeads []*bead.Bead +} + +func (m *mockRevalidator) Revalidate(_ context.Context, beads []*bead.Bead, _ string) ([]*bead.Bead, error) { + m.calls++ + m.receivedBeads = append(m.receivedBeads, beads...) + return beads, nil +} diff --git a/internal/v2/loop/spec_loop.go b/internal/v2/loop/spec_loop.go index 86c11e64f..817fe2f51 100644 --- a/internal/v2/loop/spec_loop.go +++ b/internal/v2/loop/spec_loop.go @@ -15,6 +15,7 @@ import ( "github.com/danabrams/gromit/internal/events" "github.com/danabrams/gromit/internal/v2/adapter" "github.com/danabrams/gromit/internal/v2/event" + "github.com/danabrams/gromit/internal/v2/routing" "github.com/danabrams/gromit/internal/v2/trackertypes" "github.com/danabrams/gromit/internal/v2/presentation" v2review "github.com/danabrams/gromit/internal/v2/review" @@ -56,7 +57,20 @@ type BeadRunner interface { } type remediationRunner interface { - Run(ctx context.Context, specID string) error + Run(ctx context.Context, specID, worktree string) error +} + +// SelectiveRevalidator checks beads for regressions and returns the subset +// that failed validation and need to be re-queued in the bead loop. +type SelectiveRevalidator interface { + Revalidate(ctx context.Context, beads []*bead.Bead, worktree string) ([]*bead.Bead, error) +} + +// GapAnalyzer provides the data needed for resume gap analysis: the set of +// files that changed since the last run and a mapping from bead ID to the +// files each bead touched. +type GapAnalyzer interface { + Analyze(ctx context.Context, worktree string, beads []*bead.Bead) (changedFiles []string, beadFileMap map[string][]string, err error) } // SpecLoopOption configures optional behavior when constructing a SpecLoop. @@ -125,6 +139,36 @@ func WithPlanStage(stage stagepkg.Stage) SpecLoopOption { } } +// WithSelectiveRevalidator installs a revalidator that checks completed beads +// for regressions before the bead loop starts on resume. +func WithSelectiveRevalidator(r SelectiveRevalidator) SpecLoopOption { + return func(s *SpecLoop) { + s.selectiveRevalidator = r + } +} + +// WithGapAnalyzer installs the gap analyzer used to identify which completed +// beads have changed files and need selective revalidation on resume. +func WithGapAnalyzer(a GapAnalyzer) SpecLoopOption { + return func(s *SpecLoop) { + s.gapAnalyzer = a + } +} + +// WithRouter configures the routing engine for provider/model selection. +func WithRouter(r *routing.Router) SpecLoopOption { + return func(s *SpecLoop) { + s.router = r + } +} + +// WithPhaseModels sets the phase-to-tier mapping for routing. +func WithPhaseModels(pm map[string]string) SpecLoopOption { + return func(s *SpecLoop) { + s.phaseModels = pm + } +} + // WithPreserveOnFailure controls whether the worktree is kept when the spec // fails. The default is true (preserve). Pass false to remove it on failure. func WithPreserveOnFailure(preserve bool) SpecLoopOption { @@ -167,6 +211,10 @@ type SpecLoop struct { presentStage stagepkg.Stage presentSummaryContext *present.SummaryContext preserveOnFailure bool // restore t.Cleanup if overriding in tests + selectiveRevalidator SelectiveRevalidator + gapAnalyzer GapAnalyzer + router *routing.Router + phaseModels map[string]string } type worktreeSetter interface { @@ -257,6 +305,7 @@ func (s *SpecLoop) Run(ctx context.Context, specID string, stopCh <-chan struct{ plan = string(existingPlan) s.emit(&events.PlanResumedEvent{SpecID: specID, Path: planPath}) } else { + s.applyRouting(&req, "plan") planRes, err := s.runPlanStage(ctx, req) if err != nil { return fmt.Errorf("plan stage: %w", err) @@ -295,7 +344,37 @@ func (s *SpecLoop) Run(ctx context.Context, specID string, stopCh <-chan struct{ if len(existingBeads) > 0 { beads = existingBeads s.emit(&events.DecomposeResumedEvent{SpecID: specID, BeadCount: len(beads)}) + if s.selectiveRevalidator != nil { + // Gap analysis: identify which beads touched files that changed. + revalidationCandidates := beads + if s.gapAnalyzer != nil { + changedFiles, beadFileMap, err := s.gapAnalyzer.Analyze(ctx, worktree, beads) + if err != nil { + return fmt.Errorf("gap analysis: %w", err) + } + if flagged := FlagChangedBeads(beads, changedFiles, beadFileMap); len(flagged) > 0 { + revalidationCandidates = flagged + } + } + requeueBeads, err := s.selectiveRevalidator.Revalidate(ctx, revalidationCandidates, worktree) + if err != nil { + return fmt.Errorf("selective revalidation: %w", err) + } + // Merge re-queued beads: mark existing ones in-place, append truly new ones. + existingIDs := make(map[string]*bead.Bead, len(beads)) + for _, b := range beads { + existingIDs[b.ID] = b + } + for _, rb := range requeueBeads { + if existing, ok := existingIDs[rb.ID]; ok { + existing.Status = "open" + } else { + beads = append(beads, rb) + } + } + } } else { + s.applyRouting(&req, "decompose") beads, err = s.runDecompose(ctx, req) if err != nil { return err @@ -450,6 +529,7 @@ func (s *SpecLoop) ensureAcceptance(ctx context.Context, req *stagepkg.Request, return nil, err } + s.applyRouting(req, "accept") res, err := s.runAcceptStage(ctx, req) if err != nil { return res, err @@ -463,7 +543,7 @@ func (s *SpecLoop) ensureAcceptance(ctx context.Context, req *stagepkg.Request, if retriesRemaining <= 0 { return res, fmt.Errorf("%w: limit %d reached", ErrAcceptanceRetriesExceeded, maxAcceptanceRetries) } - if err := s.remediationRunner.Run(ctx, specID); err != nil { + if err := s.remediationRunner.Run(ctx, specID, req.Worktree); err != nil { return res, err } retriesRemaining-- @@ -725,3 +805,16 @@ func (s *SpecLoop) ctxErr(ctx context.Context) error { return nil } } + +// applyRouting populates req.Provider and req.Model via the Router, if configured. +func (s *SpecLoop) applyRouting(req *stagepkg.Request, phase string) { + if s.router == nil { + return + } + tier := routing.TierForPhase(phase, s.phaseModels, routing.TierMedium) + provider, model, err := s.router.Select(phase, tier) + if err == nil && provider != nil { + req.Provider = provider + req.Model = model + } +} diff --git a/internal/v2/loop/spec_loop_helpers_test.go b/internal/v2/loop/spec_loop_helpers_test.go index 69b98fecf..902e1f1e1 100644 --- a/internal/v2/loop/spec_loop_helpers_test.go +++ b/internal/v2/loop/spec_loop_helpers_test.go @@ -6,10 +6,12 @@ import ( "os" "path/filepath" "reflect" + "strings" "sync" "testing" "time" + "github.com/danabrams/gromit/internal/bead" "github.com/danabrams/gromit/internal/events" "github.com/danabrams/gromit/internal/v2/adapter" "github.com/danabrams/gromit/internal/v2/adapter/llm" @@ -18,6 +20,41 @@ import ( stagepkg "github.com/danabrams/gromit/internal/v2/stage" ) +// requireGapAnalysisInFailureSummary asserts the presenter's last failure summary contains wantSubstr. +func requireGapAnalysisInFailureSummary(t *testing.T, presenter *fakePresenterAdapter, wantSubstr string) { + t.Helper() + summary := presenter.lastSummary + if summary.FailureSummary == "" { + t.Fatal("failure summary is empty") + } + if !strings.Contains(summary.FailureSummary, wantSubstr) { + t.Fatalf("failure summary = %q, want to contain %q", summary.FailureSummary, wantSubstr) + } +} + +// requireBeadIDs asserts that the bead runner received exactly the given bead IDs in order. +func requireBeadIDs(t *testing.T, runner *fakeBeadRunner, wantIDs []string) { + t.Helper() + if len(runner.lastBeads) != len(wantIDs) { + t.Fatalf("bead runner received %d beads, want %d: got IDs %v", len(runner.lastBeads), len(wantIDs), beadIDs(runner.lastBeads)) + } + for i, want := range wantIDs { + if runner.lastBeads[i].ID != want { + t.Fatalf("bead[%d].ID = %q, want %q", i, runner.lastBeads[i].ID, want) + } + } +} + +func beadIDs(beads []*bead.Bead) []string { + ids := make([]string, 0, len(beads)) + for _, b := range beads { + if b != nil { + ids = append(ids, b.ID) + } + } + return ids +} + func requireStageSequence(t *testing.T, recorder *recordingStageRecorder) { t.Helper() if got := recorder.stageNames(); !reflect.DeepEqual(got, StageSequence) { @@ -234,11 +271,26 @@ type fakeRemediationRunner struct { err error } -func (f *fakeRemediationRunner) Run(_ context.Context, _ string) error { +func (f *fakeRemediationRunner) Run(_ context.Context, _, _ string) error { f.calls++ return f.err } +// recordingRemediationRunner captures the specID and worktree passed to Run. +type recordingRemediationRunner struct { + calls int + lastSpecID string + lastWorktree string + err error +} + +func (r *recordingRemediationRunner) Run(_ context.Context, specID, worktree string) error { + r.calls++ + r.lastSpecID = specID + r.lastWorktree = worktree + return r.err +} + type scriptedAcceptStage struct { calls int results []stagepkg.Result diff --git a/internal/v2/loop/spec_loop_test.go b/internal/v2/loop/spec_loop_test.go index 908756841..72b1e900e 100644 --- a/internal/v2/loop/spec_loop_test.go +++ b/internal/v2/loop/spec_loop_test.go @@ -1306,6 +1306,157 @@ func TestSpecLoopCreatesEventsFileWhenTypedEmitterSet(t *testing.T) { } } +func TestResumeWithGapAnalysis_FailureSummaryPopulated(t *testing.T) { + t.Parallel() + + ctx := context.Background() + specID := "spec-resume-gap-analysis" + cfg := &config.Config{} + + git := newFakeGitAdapter(t) + git.planContent = "existing plan" + git.gapAnalysisContent = "remaining: implement step X" + + taskTracker := newFakeTaskTrackerAdapter() + taskTracker.queryBeadsResponse = &tasktracker.TaskTrackerQueryBeadsResponse{ + Beads: []tasktracker.Bead{ + {ID: "bead-remaining", Title: "Remaining bead"}, + }, + } + + fp := newFakePresenterAdapter(t) + presentStage, summaryCtx := newPresentStageForTest(t, cfg, fp) + + // Accept fails once with no remediation runner — triggers handleFailure + gap analysis. + accept := newScriptedAcceptStage(stagepkg.Result{Decision: stagepkg.DecisionFail}) + + loopInstance, err := NewSpecLoop( + adapter.AdapterSet{ + Git: git, + LLM: newFakeLLMAdapter(), + TaskTracker: taskTracker, + Presenter: fp, + }, + cfg, noopDependencyGate{}, + WithPlanStage(newFakePlanStage(specID)), + WithPresentStage(presentStage, summaryCtx), + WithDecomposeStage(newFakeDecomposeStage(specID)), + WithBeadLoop(newFakeBeadRunner()), + WithAcceptStage(accept), + ) + if err != nil { + t.Fatalf("create spec loop: %v", err) + } + + if err := loopInstance.Run(ctx, specID, nil); err == nil { + t.Fatal("expected failure from accept stage") + } + + requireGapAnalysisInFailureSummary(t, fp, "remaining: implement step X") +} + +func TestResumeWithExistingPlanAndBeads_BeadListCorrect(t *testing.T) { + t.Parallel() + + ctx := context.Background() + specID := "spec-resume-plan-and-beads" + cfg := &config.Config{} + + git := newFakeGitAdapter(t) + git.planContent = "existing plan from prior run" + + taskTracker := newFakeTaskTrackerAdapter() + taskTracker.queryBeadsResponse = &tasktracker.TaskTrackerQueryBeadsResponse{ + Beads: []tasktracker.Bead{ + {ID: "bead-alpha", Title: "Alpha bead"}, + {ID: "bead-beta", Title: "Beta bead"}, + }, + } + + planStage := newFakePlanStage(specID) + decompose := newFakeDecomposeStage(specID) + beadRunner := newFakeBeadRunner() + + loopInstance, err := NewSpecLoop( + adapter.AdapterSet{ + Git: git, + LLM: newFakeLLMAdapter(), + TaskTracker: taskTracker, + Presenter: newFakePresenterAdapter(t), + }, + cfg, noopDependencyGate{}, + WithPlanStage(planStage), + WithPresentStage(newFakePresentStage(), &present.SummaryContext{}), + WithDecomposeStage(decompose), + WithBeadLoop(beadRunner), + WithAcceptStage(newFakeAcceptStage()), + ) + if err != nil { + t.Fatalf("create spec loop: %v", err) + } + + if err := loopInstance.Run(ctx, specID, nil); err != nil { + t.Fatalf("run spec loop: %v", err) + } + + if planStage.called { + t.Fatal("plan stage should not be called when plan file exists") + } + if decompose.called { + t.Fatal("decompose stage should not be called when beads already exist") + } + requireBeadIDs(t, beadRunner, []string{"bead-alpha", "bead-beta"}) +} + +func TestRemediationRunnerReceivesCorrectWorktree(t *testing.T) { + t.Parallel() + + ctx := context.Background() + specID := "spec-remediation-worktree" + cfg := &config.Config{} + + git := newFakeGitAdapter(t) + runner := &recordingRemediationRunner{} + + accept := newScriptedAcceptStage( + stagepkg.Result{Decision: stagepkg.DecisionFail}, + stagepkg.Result{Decision: stagepkg.DecisionProceed}, + ) + + loopInstance, err := NewSpecLoop( + adapter.AdapterSet{ + Git: git, + LLM: newFakeLLMAdapter(), + TaskTracker: newFakeTaskTrackerAdapter(), + Presenter: newFakePresenterAdapter(t), + }, + cfg, noopDependencyGate{}, + WithPlanStage(newFakePlanStage(specID)), + WithPresentStage(newFakePresentStage(), &present.SummaryContext{}), + WithDecomposeStage(newFakeDecomposeStage(specID)), + WithBeadLoop(newFakeBeadRunner()), + WithAcceptStage(accept), + WithRemediationRunner(runner), + ) + if err != nil { + t.Fatalf("create spec loop: %v", err) + } + + if err := loopInstance.Run(ctx, specID, nil); err != nil { + t.Fatalf("run spec loop: %v", err) + } + + if runner.calls != 1 { + t.Fatalf("remediation calls = %d, want 1", runner.calls) + } + if runner.lastWorktree != git.lastWorktree { + t.Fatalf("remediation worktree = %q, want %q", runner.lastWorktree, git.lastWorktree) + } + if runner.lastSpecID != specID { + t.Fatalf("remediation specID = %q, want %q", runner.lastSpecID, specID) + } +} + func newPresentStageForTest(t *testing.T, cfg *config.Config, presenter adapter.PresenterAdapter) (stagepkg.Stage, *present.SummaryContext) { t.Helper() summaryCtx := &present.SummaryContext{} @@ -1315,3 +1466,170 @@ func newPresentStageForTest(t *testing.T, cfg *config.Config, presenter adapter. } return stage, summaryCtx } + +func TestSelectiveRevalidation_RequeuesFailedBeads(t *testing.T) { + t.Parallel() + + ctx := context.Background() + specID := "spec-revalidation" + cfg := &config.Config{} + + git := newFakeGitAdapter(t) + taskTracker := newFakeTaskTrackerAdapter() + taskTracker.queryBeadsResponse = &tasktracker.TaskTrackerQueryBeadsResponse{ + Beads: []tasktracker.Bead{ + {ID: "bead-open-1", Title: "Open bead"}, + }, + } + + revalidatedBead := &bead.Bead{ID: "bead-completed-failed", Title: "Regressed bead"} + revalidator := &fakeSelectiveRevalidator{requeueBeads: []*bead.Bead{revalidatedBead}} + + planStage := newFakePlanStage(specID) + decompose := newFakeDecomposeStage(specID) + beadRunner := newFakeBeadRunner() + accept := newFakeAcceptStage() + presentStage := newFakePresentStage() + summaryCtx := &present.SummaryContext{} + + adapters := adapter.AdapterSet{ + Git: git, + LLM: newFakeLLMAdapter(), + TaskTracker: taskTracker, + Presenter: newFakePresenterAdapter(t), + } + + loopInstance, err := NewSpecLoop(adapters, cfg, noopDependencyGate{}, + WithPlanStage(planStage), + WithPresentStage(presentStage, summaryCtx), + WithDecomposeStage(decompose), + WithBeadLoop(beadRunner), + WithAcceptStage(accept), + WithSelectiveRevalidator(revalidator), + ) + if err != nil { + t.Fatalf("create spec loop: %v", err) + } + + if err := loopInstance.Run(ctx, specID, nil); err != nil { + t.Fatalf("run spec loop: %v", err) + } + + wantIDs := []string{"bead-open-1", "bead-completed-failed"} + requireBeadIDs(t, beadRunner, wantIDs) +} + +func TestSelectiveRevalidation_ErrorPropagates(t *testing.T) { + t.Parallel() + + ctx := context.Background() + specID := "spec-revalidation-error" + cfg := &config.Config{} + + git := newFakeGitAdapter(t) + taskTracker := newFakeTaskTrackerAdapter() + taskTracker.queryBeadsResponse = &tasktracker.TaskTrackerQueryBeadsResponse{ + Beads: []tasktracker.Bead{ + {ID: "bead-1", Title: "A bead"}, + }, + } + + revalidator := &fakeSelectiveRevalidator{err: fmt.Errorf("validation infrastructure failed")} + + planStage := newFakePlanStage(specID) + decompose := newFakeDecomposeStage(specID) + beadRunner := newFakeBeadRunner() + accept := newFakeAcceptStage() + presentStage := newFakePresentStage() + summaryCtx := &present.SummaryContext{} + + adapters := adapter.AdapterSet{ + Git: git, + LLM: newFakeLLMAdapter(), + TaskTracker: taskTracker, + Presenter: newFakePresenterAdapter(t), + } + + loopInstance, err := NewSpecLoop(adapters, cfg, noopDependencyGate{}, + WithPlanStage(planStage), + WithPresentStage(presentStage, summaryCtx), + WithDecomposeStage(decompose), + WithBeadLoop(beadRunner), + WithAcceptStage(accept), + WithSelectiveRevalidator(revalidator), + ) + if err != nil { + t.Fatalf("create spec loop: %v", err) + } + + err = loopInstance.Run(ctx, specID, nil) + if err == nil { + t.Fatal("expected error from revalidator, got nil") + } + if !strings.Contains(err.Error(), "selective revalidation") { + t.Fatalf("error = %q, want to contain 'selective revalidation'", err.Error()) + } +} + +func TestSelectiveRevalidation_SkippedOnFreshRun(t *testing.T) { + t.Parallel() + + ctx := context.Background() + specID := "spec-fresh-revalidation" + cfg := &config.Config{} + + git := newFakeGitAdapter(t) + taskTracker := newFakeTaskTrackerAdapter() + // No queryBeadsResponse — fresh run, no existing beads + + revalidator := &fakeSelectiveRevalidator{} + + planStage := newFakePlanStage(specID) + decompose := newFakeDecomposeStage(specID) + beadRunner := newFakeBeadRunner() + accept := newFakeAcceptStage() + presentStage := newFakePresentStage() + summaryCtx := &present.SummaryContext{} + + adapters := adapter.AdapterSet{ + Git: git, + LLM: newFakeLLMAdapter(), + TaskTracker: taskTracker, + Presenter: newFakePresenterAdapter(t), + } + + loopInstance, err := NewSpecLoop(adapters, cfg, noopDependencyGate{}, + WithPlanStage(planStage), + WithPresentStage(presentStage, summaryCtx), + WithDecomposeStage(decompose), + WithBeadLoop(beadRunner), + WithAcceptStage(accept), + WithSelectiveRevalidator(revalidator), + ) + if err != nil { + t.Fatalf("create spec loop: %v", err) + } + + if err := loopInstance.Run(ctx, specID, nil); err != nil { + t.Fatalf("run spec loop: %v", err) + } + + if revalidator.calls != 0 { + t.Fatalf("revalidator called %d times on fresh run, want 0", revalidator.calls) + } +} + +type fakeSelectiveRevalidator struct { + requeueBeads []*bead.Bead + err error + calls int + lastBeads []*bead.Bead + lastWorktree string +} + +func (f *fakeSelectiveRevalidator) Revalidate(ctx context.Context, beads []*bead.Bead, worktree string) ([]*bead.Bead, error) { + f.calls++ + f.lastBeads = append([]*bead.Bead(nil), beads...) + f.lastWorktree = worktree + return f.requeueBeads, f.err +} diff --git a/internal/v2/loop/topo.go b/internal/v2/loop/topo.go new file mode 100644 index 000000000..705c48440 --- /dev/null +++ b/internal/v2/loop/topo.go @@ -0,0 +1,128 @@ +package loop + +import ( + "fmt" + "strings" + + "github.com/danabrams/gromit/internal/bead" + "github.com/danabrams/gromit/internal/queue" +) + +// TopologicalSort returns beads sorted so all dependencies appear before their +// dependents. Beads with no dependency relationship preserve their original +// relative order (stable). Returns an error if a dependency cycle is detected. +func TopologicalSort(beads []*bead.Bead) ([]*bead.Bead, error) { + // Build an index of bead position for stable ordering + index := make(map[string]int, len(beads)) + for i, b := range beads { + if b == nil { + continue + } + index[b.ID] = i + } + + // Compute indegrees and adjacency within the provided set + indegree := make(map[string]int, len(beads)) + dependents := make(map[string][]string, len(beads)) + + for _, b := range beads { + if b == nil { + continue + } + if _, ok := indegree[b.ID]; !ok { + indegree[b.ID] = 0 + } + } + + for _, b := range beads { + if b == nil { + continue + } + deps := beadDependencyIDs(b) + for _, dep := range deps { + if dep == "" { + continue + } + if _, ok := indegree[dep]; !ok { + // dep not in set — treat as already satisfied + continue + } + indegree[b.ID]++ + dependents[dep] = append(dependents[dep], b.ID) + } + } + + // Kahn's algorithm; break ties by original position for stable output + ready := make([]string, 0, len(beads)) + for _, b := range beads { + if b == nil { + continue + } + if indegree[b.ID] == 0 { + ready = append(ready, b.ID) + } + } + + beadByID := make(map[string]*bead.Bead, len(beads)) + for _, b := range beads { + if b != nil { + beadByID[b.ID] = b + } + } + + result := make([]*bead.Bead, 0, len(beads)) + for len(ready) > 0 { + // Pick next by original position (stable) + minIdx := 0 + for i := 1; i < len(ready); i++ { + if index[ready[i]] < index[ready[minIdx]] { + minIdx = i + } + } + cur := ready[minIdx] + ready = append(ready[:minIdx], ready[minIdx+1:]...) + result = append(result, beadByID[cur]) + + for _, dep := range dependents[cur] { + indegree[dep]-- + if indegree[dep] == 0 { + ready = append(ready, dep) + } + } + } + + // Count non-nil beads to check for cycle + total := 0 + for _, b := range beads { + if b != nil { + total++ + } + } + if len(result) != total { + return nil, fmt.Errorf("cycle detected in bead dependency graph") + } + + return result, nil +} + +// beadDependencyIDs collects all dependency IDs from a bead's dependency fields. +func beadDependencyIDs(b *bead.Bead) []string { + if b == nil { + return nil + } + var deps []string + deps = append(deps, queue.DependencyIDs(b.DependsOn)...) + deps = append(deps, queue.DependencyIDs(b.Dependencies)...) + deps = append(deps, queue.DependencyIDs(b.BlockedBy)...) + return filterEmpty(deps) +} + +func filterEmpty(ss []string) []string { + out := make([]string, 0, len(ss)) + for _, s := range ss { + if strings.TrimSpace(s) != "" { + out = append(out, s) + } + } + return out +} diff --git a/internal/v2/loop/topo_test.go b/internal/v2/loop/topo_test.go new file mode 100644 index 000000000..34044cf53 --- /dev/null +++ b/internal/v2/loop/topo_test.go @@ -0,0 +1,152 @@ +package loop + +import ( + "testing" + + "github.com/danabrams/gromit/internal/bead" +) + +func TestTopologicalSort_BlockedByHonored(t *testing.T) { + t.Parallel() + + // "consumer" is blocked_by "provider" — provider must come first. + beads := []*bead.Bead{ + {ID: "consumer", BlockedBy: []bead.Dependency{{ID: "provider"}}}, + {ID: "provider"}, + } + + got, err := TopologicalSort(beads) + if err != nil { + t.Fatalf("TopologicalSort: %v", err) + } + if len(got) != 2 { + t.Fatalf("got %d beads, want 2", len(got)) + } + if got[0].ID != "provider" || got[1].ID != "consumer" { + t.Fatalf("order = [%s %s], want [provider consumer]", got[0].ID, got[1].ID) + } +} + +func TestTopologicalSort_StableTieBreaking(t *testing.T) { + t.Parallel() + + // Three independent beads — output must preserve input order. + beads := []*bead.Bead{ + {ID: "c"}, + {ID: "a"}, + {ID: "b"}, + } + + got, err := TopologicalSort(beads) + if err != nil { + t.Fatalf("TopologicalSort: %v", err) + } + if len(got) != 3 { + t.Fatalf("got %d beads, want 3", len(got)) + } + want := []string{"c", "a", "b"} + for i, w := range want { + if got[i].ID != w { + t.Fatalf("position %d = %q, want %q", i, got[i].ID, w) + } + } +} + +func TestTopologicalSort_CycleReturnsError(t *testing.T) { + t.Parallel() + + beads := []*bead.Bead{ + {ID: "a", DependsOn: []bead.Dependency{{ID: "b"}}}, + {ID: "b", DependsOn: []bead.Dependency{{ID: "a"}}}, + } + + _, err := TopologicalSort(beads) + if err == nil { + t.Fatal("expected error for cycle, got nil") + } +} + +func TestTopologicalSort_DependencyBeforeDependent(t *testing.T) { + t.Parallel() + + beads := []*bead.Bead{ + {ID: "child", DependsOn: []bead.Dependency{{ID: "root"}}}, + {ID: "root"}, + } + + got, err := TopologicalSort(beads) + if err != nil { + t.Fatalf("TopologicalSort: %v", err) + } + if len(got) != 2 { + t.Fatalf("got %d beads, want 2", len(got)) + } + if got[0].ID != "root" || got[1].ID != "child" { + t.Fatalf("order = [%s %s], want [root child]", got[0].ID, got[1].ID) + } +} + +func TestTopologicalSort_DependenciesFieldHonored(t *testing.T) { + t.Parallel() + + // Uses the Dependencies field (not DependsOn or BlockedBy) — all three must be collected. + beads := []*bead.Bead{ + {ID: "child", Dependencies: []bead.Dependency{{ID: "root"}}}, + {ID: "root"}, + } + + got, err := TopologicalSort(beads) + if err != nil { + t.Fatalf("TopologicalSort: %v", err) + } + if len(got) != 2 { + t.Fatalf("got %d beads, want 2", len(got)) + } + if got[0].ID != "root" || got[1].ID != "child" { + t.Fatalf("order = [%s %s], want [root child]", got[0].ID, got[1].ID) + } +} + +func TestTopologicalSort_ExternalDepTreatedAsSatisfied(t *testing.T) { + t.Parallel() + + // "b" depends on "external" which is not in the input set. + // The sort must not error and must include both beads. + beads := []*bead.Bead{ + {ID: "b", DependsOn: []bead.Dependency{{ID: "external"}}}, + {ID: "a"}, + } + + got, err := TopologicalSort(beads) + if err != nil { + t.Fatalf("TopologicalSort: unexpected error: %v", err) + } + if len(got) != 2 { + t.Fatalf("got %d beads, want 2", len(got)) + } +} + +func TestTopologicalSort_ChainOrderingThreeLevels(t *testing.T) { + t.Parallel() + + // a depends on b, b depends on c → order must be c, b, a. + beads := []*bead.Bead{ + {ID: "a", DependsOn: []bead.Dependency{{ID: "b"}}}, + {ID: "b", DependsOn: []bead.Dependency{{ID: "c"}}}, + {ID: "c"}, + } + + got, err := TopologicalSort(beads) + if err != nil { + t.Fatalf("TopologicalSort: %v", err) + } + want := []string{"c", "b", "a"} + if len(got) != len(want) { + t.Fatalf("got %d beads, want %d", len(got), len(want)) + } + for i, w := range want { + if got[i].ID != w { + t.Fatalf("position %d = %q, want %q", i, got[i].ID, w) + } + } +} diff --git a/internal/v2/prompt/assembler.go b/internal/v2/prompt/assembler.go index 3158cc969..d976a2f51 100644 --- a/internal/v2/prompt/assembler.go +++ b/internal/v2/prompt/assembler.go @@ -2,12 +2,34 @@ package prompt import "strings" +// DefaultMaxChars is the default maximum character budget for shaped prompts. +const DefaultMaxChars = 100000 + +// BeadInfo carries bead metadata used for prompt shaping. +type BeadInfo struct { + Title string + Files []string + FileCount int +} + +// ShapeReport records budget shaping decisions for observability. +type ShapeReport struct { + OriginalSize int + ShapedSize int + MaxBudget int + AdjustedBudget int + FileCount int + Trimmed bool + TrimmedBytes int +} + // PromptAssembler merges prompt layers into a single payload. type PromptAssembler struct { - base string - project string - instance string - fragment string + base string + project string + instance string + fragment string + lastReport *ShapeReport } // NewPromptAssembler returns an assembler initialized with the provided layers. @@ -15,14 +37,75 @@ func NewPromptAssembler(base, project, instance, fragment string) *PromptAssembl return &PromptAssembler{base: base, project: project, instance: instance, fragment: fragment} } -// Assemble concatenates the configured layers in the prescribed order. -func (p *PromptAssembler) Assemble() string { +// LastReport returns the ShapeReport from the most recent Assemble call, or nil. +func (p *PromptAssembler) LastReport() *ShapeReport { + return p.lastReport +} + +// loadBaseInstructions returns base instructions filtered by phase. +// When phase is empty, the full base text is returned unmodified. +func (p *PromptAssembler) loadBaseInstructions(phase string) string { + if phase == "" { + return p.base + } + // Phase-specific filtering: look for phase markers and extract the relevant section. + marker := "## " + phase + idx := strings.Index(p.base, marker) + if idx == -1 { + // No phase-specific section found; return full base. + return p.base + } + // Extract from the marker to the next "## " heading or end of string. + rest := p.base[idx:] + nextIdx := strings.Index(rest[len(marker):], "\n## ") + if nextIdx == -1 { + return rest + } + return rest[:len(marker)+nextIdx] +} + +// loadProjectContext returns project context scoped to the bead's files. +// When the bead has no files, the full project text is returned unmodified. +func (p *PromptAssembler) loadProjectContext(bead BeadInfo) string { + if len(bead.Files) == 0 { + return p.project + } + // Scope project context: only include lines that reference bead files + // or non-file-specific lines. This is a best-effort filter. + lines := strings.Split(p.project, "\n") + var filtered []string + for _, line := range lines { + relevant := true + // If the line looks like a file-scoped entry (contains a path-like token), + // check if it references one of the bead's files. + if strings.Contains(line, "/") { + relevant = false + for _, f := range bead.Files { + if strings.Contains(line, f) { + relevant = true + break + } + } + } + if relevant { + filtered = append(filtered, line) + } + } + return strings.Join(filtered, "\n") +} + +// Assemble concatenates the configured layers in the prescribed order, +// applying phase filtering, bead scoping, and budget shaping. +func (p *PromptAssembler) Assemble(phase string, bead BeadInfo) string { + base := p.loadBaseInstructions(phase) + project := p.loadProjectContext(bead) + layers := []struct { name string content string }{ - {"BASE", p.base}, - {"PROJECT", p.project}, + {"BASE", base}, + {"PROJECT", project}, {"INSTANCE", p.instance}, {"FRAGMENT", p.fragment}, } @@ -41,5 +124,56 @@ func (p *PromptAssembler) Assemble() string { builder.WriteString(layer.content) } - return builder.String() + raw := builder.String() + + fileCount := bead.FileCount + if fileCount == 0 { + fileCount = len(bead.Files) + } + + shaped, report := ShapeBudget(raw, fileCount, DefaultMaxChars) + p.lastReport = &report + + return shaped +} + +// ShapeBudget applies scope-adjusted truncation to a prompt string. +// +// Scaling rules based on fileCount: +// +// 1-2 files → 50% of maxChars +// 3-4 files → 75% of maxChars +// 5+ files or 0 (unknown) → 100% of maxChars +// +// When the prompt exceeds the adjusted budget, context (tail) is truncated +// to fit while preserving instructions (head). +func ShapeBudget(total string, fileCount int, maxChars int) (string, ShapeReport) { + adjustedBudget := maxChars + switch { + case fileCount >= 1 && fileCount <= 2: + adjustedBudget = maxChars / 2 + case fileCount >= 3 && fileCount <= 4: + adjustedBudget = maxChars * 3 / 4 + } + + report := ShapeReport{ + OriginalSize: len(total), + MaxBudget: maxChars, + AdjustedBudget: adjustedBudget, + FileCount: fileCount, + } + + if len(total) <= adjustedBudget { + report.ShapedSize = len(total) + report.Trimmed = false + report.TrimmedBytes = 0 + return total, report + } + + // Truncate from the end (context) to preserve instructions (head). + shaped := total[:adjustedBudget] + report.ShapedSize = adjustedBudget + report.Trimmed = true + report.TrimmedBytes = len(total) - adjustedBudget + return shaped, report } diff --git a/internal/v2/prompt/assembler_test.go b/internal/v2/prompt/assembler_test.go index cc71c49c5..5ae06a08d 100644 --- a/internal/v2/prompt/assembler_test.go +++ b/internal/v2/prompt/assembler_test.go @@ -7,7 +7,7 @@ import ( func TestPromptAssemblerAddsLayerMarkers(t *testing.T) { assembler := NewPromptAssembler("base layer", "project layer", "instance layer", "fragment layer") - output := assembler.Assemble() + output := assembler.Assemble("", BeadInfo{}) expectedSequence := []string{ "=== BASE ===", @@ -35,7 +35,7 @@ func TestPromptAssemblerAddsLayerMarkers(t *testing.T) { func TestPromptAssemblerSkipsEmptyLayers(t *testing.T) { assembler := NewPromptAssembler("base layer", "", "instance layer", "") - output := assembler.Assemble() + output := assembler.Assemble("", BeadInfo{}) if !strings.Contains(output, "=== BASE ===") { t.Fatalf("base section missing") @@ -53,7 +53,7 @@ func TestPromptAssemblerSkipsEmptyLayers(t *testing.T) { func TestPromptAssemblerSkipsWhitespaceOnlyLayers(t *testing.T) { assembler := NewPromptAssembler("base layer", " \n\t", "instance layer", "fragment layer") - output := assembler.Assemble() + output := assembler.Assemble("", BeadInfo{}) if strings.Contains(output, "=== PROJECT ===") { t.Fatalf("project section should be omitted when it contains only whitespace") @@ -68,3 +68,92 @@ func TestPromptAssemblerSkipsWhitespaceOnlyLayers(t *testing.T) { t.Fatalf("fragment section missing") } } + +func TestShapeBudgetSmallBeadGets50Percent(t *testing.T) { + content := strings.Repeat("x", 10000) + shaped, report := ShapeBudget(content, 1, 10000) + + if len(shaped) > 5000 { + t.Fatalf("shaped len = %d, want <= 5000 (50%% of 10000)", len(shaped)) + } + if !report.Trimmed { + t.Fatal("expected Trimmed = true") + } + if report.AdjustedBudget != 5000 { + t.Fatalf("AdjustedBudget = %d, want 5000", report.AdjustedBudget) + } +} + +func TestShapeBudgetMediumBeadGets75Percent(t *testing.T) { + content := strings.Repeat("x", 10000) + shaped, report := ShapeBudget(content, 3, 10000) + + if len(shaped) > 7500 { + t.Fatalf("shaped len = %d, want <= 7500 (75%% of 10000)", len(shaped)) + } + if !report.Trimmed { + t.Fatal("expected Trimmed = true") + } + if report.AdjustedBudget != 7500 { + t.Fatalf("AdjustedBudget = %d, want 7500", report.AdjustedBudget) + } +} + +func TestShapeBudgetLargeBeadGets100Percent(t *testing.T) { + content := strings.Repeat("x", 10000) + shaped, report := ShapeBudget(content, 6, 10000) + + if len(shaped) != 10000 { + t.Fatalf("shaped len = %d, want 10000 (100%% budget, no trim needed)", len(shaped)) + } + if report.Trimmed { + t.Fatal("expected Trimmed = false for large bead within budget") + } + if report.AdjustedBudget != 10000 { + t.Fatalf("AdjustedBudget = %d, want 10000", report.AdjustedBudget) + } +} + +func TestShapeBudgetReportsTrimmingDetails(t *testing.T) { + content := strings.Repeat("x", 10000) + _, report := ShapeBudget(content, 2, 10000) + + if report.OriginalSize != 10000 { + t.Fatalf("OriginalSize = %d, want 10000", report.OriginalSize) + } + if report.ShapedSize != 5000 { + t.Fatalf("ShapedSize = %d, want 5000", report.ShapedSize) + } + if report.TrimmedBytes != 5000 { + t.Fatalf("TrimmedBytes = %d, want 5000", report.TrimmedBytes) + } + if report.FileCount != 2 { + t.Fatalf("FileCount = %d, want 2", report.FileCount) + } +} + +func TestAssembleWithPhaseFiltersBase(t *testing.T) { + base := "## build\nbuild instructions\n\n## validate\nvalidate instructions" + assembler := NewPromptAssembler(base, "project context", "", "") + output := assembler.Assemble("build", BeadInfo{FileCount: 10}) + + if !strings.Contains(output, "build instructions") { + t.Fatal("output should contain build instructions") + } + if strings.Contains(output, "validate instructions") { + t.Fatal("output should NOT contain validate instructions when phase=build") + } +} + +func TestAssembleStoresLastReport(t *testing.T) { + assembler := NewPromptAssembler("base", "project", "", "") + assembler.Assemble("", BeadInfo{FileCount: 1}) + + report := assembler.LastReport() + if report == nil { + t.Fatal("LastReport should not be nil after Assemble") + } + if report.FileCount != 1 { + t.Fatalf("report.FileCount = %d, want 1", report.FileCount) + } +} diff --git a/internal/v2/remediation/remediation.go b/internal/v2/remediation/remediation.go index b1f78262f..26b5ef081 100644 --- a/internal/v2/remediation/remediation.go +++ b/internal/v2/remediation/remediation.go @@ -56,7 +56,7 @@ func NewRemediationRunner(cfg RemediationRunnerConfig) *RemediationRunner { } // Run executes the remediation cycle for the provided spec. -func (r *RemediationRunner) Run(ctx context.Context, specID string) error { +func (r *RemediationRunner) Run(ctx context.Context, specID, worktree string) error { r.generationCount = 0 if specID == "" { return ErrSpecIDRequired @@ -65,7 +65,7 @@ func (r *RemediationRunner) Run(ctx context.Context, specID string) error { return ErrAcceptStageRequired } - reqTemplate := stage.Request{Bead: stage.BeadInfo{ID: specID}} + reqTemplate := stage.Request{Bead: stage.BeadInfo{ID: specID}, Worktree: worktree} for { req := reqTemplate res, err := r.cfg.AcceptStage.Run(ctx, &req) diff --git a/internal/v2/remediation/remediation_runner_unit_error_test.go b/internal/v2/remediation/remediation_runner_unit_error_test.go index 7ff2522ee..6631b8a72 100644 --- a/internal/v2/remediation/remediation_runner_unit_error_test.go +++ b/internal/v2/remediation/remediation_runner_unit_error_test.go @@ -11,14 +11,14 @@ import ( func TestRemediationRunnerRunMissingSpecID(t *testing.T) { runner := runnerForSpecIDValidation() - if err := runner.Run(context.Background(), ""); !errors.Is(err, ErrSpecIDRequired) { + if err := runner.Run(context.Background(), "", ""); !errors.Is(err, ErrSpecIDRequired) { t.Fatalf("expected ErrSpecIDRequired, got %v", err) } } func TestRemediationRunnerRunMissingAcceptStage(t *testing.T) { runner := runnerForAcceptStageValidation() - if err := runner.Run(context.Background(), "spec"); !errors.Is(err, ErrAcceptStageRequired) { + if err := runner.Run(context.Background(), "spec", ""); !errors.Is(err, ErrAcceptStageRequired) { t.Fatalf("expected ErrAcceptStageRequired, got %v", err) } } @@ -40,7 +40,7 @@ func TestRemediationRunnerRunMissingBeadRunner(t *testing.T) { } runner := runnerForMissingBeadRunner(accept, decompose) - if err := runner.Run(context.Background(), "spec"); !errors.Is(err, ErrBeadRunnerRequired) { + if err := runner.Run(context.Background(), "spec", ""); !errors.Is(err, ErrBeadRunnerRequired) { t.Fatalf("expected ErrBeadRunnerRequired, got %v", err) } } @@ -52,7 +52,7 @@ func TestRemediationRunnerRunMissingDecomposeStage(t *testing.T) { } runner := runnerForMissingDecomposeStage(accept) - if err := runner.Run(context.Background(), "spec"); !errors.Is(err, ErrDecomposeStageRequired) { + if err := runner.Run(context.Background(), "spec", ""); !errors.Is(err, ErrDecomposeStageRequired) { t.Fatalf("expected ErrDecomposeStageRequired, got %v", err) } } @@ -68,7 +68,7 @@ func TestRemediationRunnerRunUnexpectedDecomposeArtifactsUnit(t *testing.T) { } runner := runnerForUnexpectedArtifacts(accept, decompose) - if err := runner.Run(context.Background(), "spec"); !errors.Is(err, ErrUnexpectedDecomposeArtifacts) { + if err := runner.Run(context.Background(), "spec", ""); !errors.Is(err, ErrUnexpectedDecomposeArtifacts) { t.Fatalf("expected ErrUnexpectedDecomposeArtifacts, got %v", err) } } diff --git a/internal/v2/remediation/remediation_test.go b/internal/v2/remediation/remediation_test.go index 9ed8238e1..9a8e9fe14 100644 --- a/internal/v2/remediation/remediation_test.go +++ b/internal/v2/remediation/remediation_test.go @@ -12,7 +12,7 @@ import ( func TestRemediationRunnerRun_requiresSpecID(t *testing.T) { runner := newRunnerForSpecValidation() - if err := runner.Run(context.Background(), ""); !errors.Is(err, ErrSpecIDRequired) { + if err := runner.Run(context.Background(), "", ""); !errors.Is(err, ErrSpecIDRequired) { t.Fatalf("expected ErrSpecIDRequired, got %v", err) } } @@ -20,7 +20,7 @@ func TestRemediationRunnerRun_requiresSpecID(t *testing.T) { func TestRemediationRunnerRun_requiresAcceptStage(t *testing.T) { runner := newRunnerWithAcceptStage(nil) - if err := runner.Run(context.Background(), "spec-id"); !errors.Is(err, ErrAcceptStageRequired) { + if err := runner.Run(context.Background(), "spec-id", ""); !errors.Is(err, ErrAcceptStageRequired) { t.Fatalf("expected ErrAcceptStageRequired, got %v", err) } } @@ -29,7 +29,7 @@ func TestRemediationRunnerRun_requiresBeadRunner(t *testing.T) { artifacts := &stage.DecomposeArtifacts{Beads: []*bead.Bead{}} runner := newRunnerForRemediationCycle(newDecisionFailStage(), newDecomposeStageReturning(artifacts), nil, 1) - if err := runner.Run(context.Background(), "spec-id"); !errors.Is(err, ErrBeadRunnerRequired) { + if err := runner.Run(context.Background(), "spec-id", ""); !errors.Is(err, ErrBeadRunnerRequired) { t.Fatalf("expected ErrBeadRunnerRequired, got %v", err) } } @@ -37,7 +37,7 @@ func TestRemediationRunnerRun_requiresBeadRunner(t *testing.T) { func TestRemediationRunnerRun_requiresDecomposeStage(t *testing.T) { runner := newRunnerForDecomposeFailure(newDecisionFailStage(), 1) - if err := runner.Run(context.Background(), "spec-id"); !errors.Is(err, ErrDecomposeStageRequired) { + if err := runner.Run(context.Background(), "spec-id", ""); !errors.Is(err, ErrDecomposeStageRequired) { t.Fatalf("expected ErrDecomposeStageRequired, got %v", err) } } @@ -45,7 +45,7 @@ func TestRemediationRunnerRun_requiresDecomposeStage(t *testing.T) { func TestRemediationRunnerRun_requiresValidDecomposeArtifacts(t *testing.T) { runner := newRunnerForUnexpectedArtifacts(newDecisionFailStage(), newDecomposeStageReturning("unexpected"), 1) - if err := runner.Run(context.Background(), "spec-id"); !errors.Is(err, ErrUnexpectedDecomposeArtifacts) { + if err := runner.Run(context.Background(), "spec-id", ""); !errors.Is(err, ErrUnexpectedDecomposeArtifacts) { t.Fatalf("expected ErrUnexpectedDecomposeArtifacts, got %v", err) } } @@ -79,7 +79,7 @@ func TestRemediationRunnerUsesDefaultGenerationCapWhenNegative(t *testing.T) { } runner := newRunnerForRemediationCycle(accept, decompose, &testBeadRunner{}, -1) - if err := runner.Run(ctx, "spec-id"); err != nil { + if err := runner.Run(ctx, "spec-id", ""); err != nil { t.Fatalf("remediation run failed: %v", err) } @@ -120,13 +120,13 @@ func TestRemediationRunnerRun_resetsGenerationCountBetweenRuns(t *testing.T) { runner := newRunnerForRemediationCycle(accept, decompose, &testBeadRunner{}, 1) // First run: should succeed (one remediation, then accept passes). - if err := runner.Run(ctx, "spec-1"); err != nil { + if err := runner.Run(ctx, "spec-1", ""); err != nil { t.Fatalf("first run failed: %v", err) } // Second run: if generationCount is not reset, the runner thinks it already // hit the cap and returns "generation cap reached" immediately. - if err := runner.Run(ctx, "spec-2"); err != nil { + if err := runner.Run(ctx, "spec-2", ""); err != nil { t.Fatalf("second run should succeed but got: %v", err) } } @@ -167,7 +167,7 @@ func TestRemediationRunnerRun_contextCancelledDuringLoop(t *testing.T) { runner := newRunnerForRemediationCycle(accept, decompose, &testBeadRunner{}, 10) - err := runner.Run(ctx, "spec-cancel") + err := runner.Run(ctx, "spec-cancel", "") if err == nil { t.Fatal("expected error when context is cancelled") } @@ -181,6 +181,28 @@ func TestRemediationRunnerRun_contextCancelledDuringLoop(t *testing.T) { } } +func TestRemediationRunnerRun_worktreePopulatedInRequest(t *testing.T) { + worktree := "/tmp/test-worktree" + var capturedWorktree string + + accept := &testStage{ + name: "accept", + run: func(ctx context.Context, req *stage.Request) (*stage.StageResult, error) { + capturedWorktree = req.Worktree + return &stage.StageResult{Decision: stage.DecisionProceed}, nil + }, + } + + runner := NewRemediationRunner(RemediationRunnerConfig{AcceptStage: accept}) + if err := runner.Run(context.Background(), "spec-id", worktree); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if capturedWorktree != worktree { + t.Fatalf("request worktree = %q, want %q", capturedWorktree, worktree) + } +} + func newRunnerForSpecValidation() *RemediationRunner { return NewRemediationRunner(RemediationRunnerConfig{}) } diff --git a/internal/v2/routing/router.go b/internal/v2/routing/router.go new file mode 100644 index 000000000..1d24165f2 --- /dev/null +++ b/internal/v2/routing/router.go @@ -0,0 +1,152 @@ +package routing + +import ( + "fmt" + "sort" + "sync" + "time" + + "github.com/danabrams/gromit/internal/v2/llmtypes" +) + +// RouterConfig holds the configuration for the Router. +type RouterConfig struct { + // Providers maps provider name to LLMProvider implementation. + Providers map[string]llmtypes.LLMProvider + // PhasePreferences maps phase name to preferred provider name. + PhasePreferences map[string]string + // Ratio maps provider name to relative invocation weight. + Ratio map[string]int + // Cooldown is the duration before an unavailable provider is re-enabled. + Cooldown time.Duration +} + +// Router selects providers for LLM invocations using phase preferences, +// ratio balancing, and fallback with cooldown. +type Router struct { + providers map[string]llmtypes.LLMProvider + phasePreferences map[string]string + ratio map[string]int + counts map[string]int + unavailable map[string]time.Time + cooldown time.Duration + mu sync.Mutex +} + +// NewRouter constructs a Router from the provided config. +func NewRouter(cfg RouterConfig) *Router { + providers := cfg.Providers + if providers == nil { + providers = map[string]llmtypes.LLMProvider{} + } + phasePreferences := cfg.PhasePreferences + if phasePreferences == nil { + phasePreferences = map[string]string{} + } + ratio := cfg.Ratio + if ratio == nil { + ratio = map[string]int{} + } + return &Router{ + providers: providers, + phasePreferences: phasePreferences, + ratio: ratio, + counts: map[string]int{}, + unavailable: map[string]time.Time{}, + cooldown: cfg.Cooldown, + } +} + +// Select picks an LLMProvider and model name for the given phase and tier. +// Phase preferences are checked first, then ratio balancing among available providers. +// Returns an error when no providers are configured or all are unavailable. +// Automatically records the invocation for ratio balancing. +func (r *Router) Select(phase, tier string) (llmtypes.LLMProvider, string, error) { + r.mu.Lock() + defer r.mu.Unlock() + + if len(r.providers) == 0 { + return nil, "", fmt.Errorf("routing: no providers configured") + } + + now := time.Now() + + // Phase preference: if a specific provider is pinned for this phase, use it. + if preferred, ok := r.phasePreferences[phase]; ok && preferred != "" { + if p, exists := r.providers[preferred]; exists { + if !r.isUnavailable(preferred, now) { + r.counts[preferred]++ + return p, tier, nil + } + } + } + + // Ratio balancing: pick the available provider most under-served relative to its weight. + chosen := r.selectByRatio(now) + if chosen == "" { + return nil, "", fmt.Errorf("routing: all providers unavailable") + } + r.counts[chosen]++ + return r.providers[chosen], tier, nil +} + +// MarkUnavailable marks the named provider as unavailable for the cooldown duration. +func (r *Router) MarkUnavailable(name string) { + r.mu.Lock() + defer r.mu.Unlock() + until := time.Now().Add(r.cooldown) + r.unavailable[name] = until +} + +// isUnavailable reports whether the named provider is currently in cooldown. +// Caller must hold r.mu. +func (r *Router) isUnavailable(name string, now time.Time) bool { + until, exists := r.unavailable[name] + if !exists { + return false + } + if now.After(until) { + delete(r.unavailable, name) + return false + } + return true +} + +// selectByRatio picks the available provider most under-served relative to its weight. +// Returns empty string when all providers are unavailable. +// Caller must hold r.mu. +func (r *Router) selectByRatio(now time.Time) string { + type candidate struct { + name string + score float64 + } + // Sort provider names for deterministic tie-breaking. + names := make([]string, 0, len(r.providers)) + for name := range r.providers { + names = append(names, name) + } + sort.Strings(names) + + var best candidate + found := false + for _, name := range names { + if r.isUnavailable(name, now) { + continue + } + weight := r.ratio[name] + if weight <= 0 { + weight = 1 + } + count := r.counts[name] + // Score: lower count relative to weight is preferred (most under-served). + score := float64(count) / float64(weight) + if !found || score < best.score { + best = candidate{name: name, score: score} + found = true + } + } + if !found { + return "" + } + return best.name +} diff --git a/internal/v2/routing/router_test.go b/internal/v2/routing/router_test.go new file mode 100644 index 000000000..6fc21c259 --- /dev/null +++ b/internal/v2/routing/router_test.go @@ -0,0 +1,153 @@ +package routing_test + +import ( + "context" + "testing" + "time" + + "github.com/danabrams/gromit/internal/v2/llmtypes" + "github.com/danabrams/gromit/internal/v2/routing" +) + +type stubProvider struct { + name string +} + +func (s *stubProvider) Invoke(_ context.Context, _ llmtypes.LLMInvokeRequest) (*llmtypes.LLMInvokeResponse, error) { + return &llmtypes.LLMInvokeResponse{}, nil +} + +func (s *stubProvider) StreamInvoke(_ context.Context, _ llmtypes.LLMStreamInvokeRequest) (*llmtypes.LLMInvokeResponse, error) { + return &llmtypes.LLMInvokeResponse{}, nil +} + +func TestRouterSelectReturnsErrorWhenNoProviders(t *testing.T) { + r := routing.NewRouter(routing.RouterConfig{}) + _, _, err := r.Select("build", "low") + if err == nil { + t.Fatal("expected error when no providers configured, got nil") + } +} + +func TestRouterMarkUnavailableCausesFallback(t *testing.T) { + providerA := &stubProvider{name: "providerA"} + providerB := &stubProvider{name: "providerB"} + + r := routing.NewRouter(routing.RouterConfig{ + Providers: map[string]llmtypes.LLMProvider{ + "providerA": providerA, + "providerB": providerB, + }, + PhasePreferences: map[string]string{ + "build": "providerA", + }, + Cooldown: 24 * time.Hour, + }) + + // Without mark, build phase should select providerA via preference. + got, _, err := r.Select("build", "low") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != llmtypes.LLMProvider(providerA) { + t.Fatalf("expected providerA before mark, got %v", got) + } + + // After marking providerA unavailable, should fall back to providerB. + r.MarkUnavailable("providerA") + got, _, err = r.Select("build", "low") + if err != nil { + t.Fatalf("unexpected error after mark: %v", err) + } + if got != llmtypes.LLMProvider(providerB) { + t.Fatalf("expected providerB after providerA marked unavailable, got %v", got) + } +} + +func TestRouterReEnablesProviderAfterCooldown(t *testing.T) { + providerA := &stubProvider{name: "providerA"} + + r := routing.NewRouter(routing.RouterConfig{ + Providers: map[string]llmtypes.LLMProvider{ + "providerA": providerA, + }, + Cooldown: time.Millisecond, + }) + + r.MarkUnavailable("providerA") + + // Immediately after marking, provider should be unavailable. + _, _, err := r.Select("build", "low") + if err == nil { + t.Fatal("expected error when only provider is unavailable, got nil") + } + + // After cooldown, provider should be available again. + time.Sleep(5 * time.Millisecond) + got, _, err := r.Select("build", "low") + if err != nil { + t.Fatalf("expected provider re-enabled after cooldown, got error: %v", err) + } + if got != llmtypes.LLMProvider(providerA) { + t.Fatalf("expected providerA after cooldown, got %v", got) + } +} + +func TestRouterSelectRatioBalancesAcrossProviders(t *testing.T) { + providerA := &stubProvider{name: "providerA"} + providerB := &stubProvider{name: "providerB"} + + r := routing.NewRouter(routing.RouterConfig{ + Providers: map[string]llmtypes.LLMProvider{ + "providerA": providerA, + "providerB": providerB, + }, + PhasePreferences: map[string]string{ + "forced": "providerA", + }, + Ratio: map[string]int{ + "providerA": 3, + "providerB": 1, + }, + }) + + // Use phase preference to force 4 invocations on providerA (Select auto-records). + for i := 0; i < 4; i++ { + _, _, err := r.Select("forced", "low") + if err != nil { + t.Fatalf("unexpected error on forced select %d: %v", i, err) + } + } + // providerA count=4, providerB count=0. + // providerA score = 4/3 ≈ 1.33, providerB score = 0/1 = 0 → B should be selected. + got, _, err := r.Select("build", "low") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != llmtypes.LLMProvider(providerB) { + t.Fatalf("expected providerB selected after providerA over-served, got %v", got) + } +} + +func TestRouterSelectUsesPhasePreference(t *testing.T) { + providerA := &stubProvider{name: "providerA"} + providerB := &stubProvider{name: "providerB"} + + r := routing.NewRouter(routing.RouterConfig{ + Providers: map[string]llmtypes.LLMProvider{ + "providerA": providerA, + "providerB": providerB, + }, + PhasePreferences: map[string]string{ + "build": "providerB", + }, + }) + + got, _, err := r.Select("build", "low") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != llmtypes.LLMProvider(providerB) { + t.Fatalf("expected providerB selected for build phase, got %v", got) + } +} diff --git a/internal/v2/routing/tier.go b/internal/v2/routing/tier.go new file mode 100644 index 000000000..9ad839c4c --- /dev/null +++ b/internal/v2/routing/tier.go @@ -0,0 +1,48 @@ +package routing + +// Tier constants for abstract model tiers. +const ( + TierLow = "low" + TierMedium = "medium" + TierHigh = "high" +) + +// escalationChain maps a starting tier to the next tier up. +var escalationChain = map[string]string{ + TierLow: TierMedium, + TierMedium: TierHigh, +} + +// EscalationTier returns the tier for the given escalation level. +// Level 0 returns startTier, level 1 returns the next tier up, etc. +func EscalationTier(startTier string, level int) string { + tier := startTier + for i := 0; i < level; i++ { + next, ok := escalationChain[tier] + if !ok { + break // already at highest tier + } + tier = next + } + return tier +} + +// ResolveModel maps an abstract tier name to a provider-specific model name. +// Uses the provider's tier-to-model map (e.g. {"low": "claude-haiku-4-5-20251001"}). +// Returns tier as-is if no mapping is found. +func ResolveModel(tier string, models map[string]string) string { + if model, ok := models[tier]; ok && model != "" { + return model + } + return tier +} + +// TierForPhase returns the effective tier for a phase. +// phaseModels maps phase name to tier name (from methodology.phase_models config). +// Falls back to fallbackTier when the phase has no configured override. +func TierForPhase(phase string, phaseModels map[string]string, fallbackTier string) string { + if tier, ok := phaseModels[phase]; ok && tier != "" { + return tier + } + return fallbackTier +} diff --git a/internal/v2/routing/tier_test.go b/internal/v2/routing/tier_test.go new file mode 100644 index 000000000..f386bdd91 --- /dev/null +++ b/internal/v2/routing/tier_test.go @@ -0,0 +1,108 @@ +package routing_test + +import ( + "testing" + + "github.com/danabrams/gromit/internal/v2/routing" +) + +func TestTierForPhaseUsesOverrideWhenConfigured(t *testing.T) { + phaseModels := map[string]string{ + "build": "low", + "red": "low", + } + + got := routing.TierForPhase("build", phaseModels, "medium") + if got != "low" { + t.Fatalf("TierForPhase(%q) = %q, want %q", "build", got, "low") + } +} + +func TestTierForPhaseFallsBackWhenNoOverride(t *testing.T) { + phaseModels := map[string]string{ + "build": "low", + } + + got := routing.TierForPhase("validate", phaseModels, "medium") + if got != "medium" { + t.Fatalf("TierForPhase(%q) = %q, want %q", "validate", got, "medium") + } +} + +func TestResolveModelMapsAbstractTierToProviderModel(t *testing.T) { + models := map[string]string{ + "low": "claude-haiku-4-5-20251001", + "medium": "claude-sonnet-4-6", + "high": "claude-opus-4-6", + } + + got := routing.ResolveModel("low", models) + if got != "claude-haiku-4-5-20251001" { + t.Fatalf("ResolveModel(%q) = %q, want %q", "low", got, "claude-haiku-4-5-20251001") + } +} + +func TestResolveModelReturnsTierWhenNoMapping(t *testing.T) { + got := routing.ResolveModel("medium", map[string]string{}) + if got != "medium" { + t.Fatalf("ResolveModel with empty map = %q, want %q", got, "medium") + } +} + +func TestTierForPhaseEmptyStringOverrideFallsBack(t *testing.T) { + phaseModels := map[string]string{ + "build": "", + } + + got := routing.TierForPhase("build", phaseModels, "medium") + if got != "medium" { + t.Fatalf("TierForPhase with empty string override = %q, want %q", got, "medium") + } +} + +func TestResolveModelEmptyStringModelFallsBack(t *testing.T) { + models := map[string]string{ + "low": "", + } + + got := routing.ResolveModel("low", models) + if got != "low" { + t.Fatalf("ResolveModel with empty string model = %q, want %q", got, "low") + } +} + +func TestEscalationTierLowToMediumToHigh(t *testing.T) { + cases := []struct { + level int + want string + }{ + {0, routing.TierLow}, + {1, routing.TierMedium}, + {2, routing.TierHigh}, + {3, routing.TierHigh}, // caps at highest + } + for _, tc := range cases { + got := routing.EscalationTier(routing.TierLow, tc.level) + if got != tc.want { + t.Errorf("EscalationTier(low, %d) = %q, want %q", tc.level, got, tc.want) + } + } +} + +func TestEscalationTierMediumToHigh(t *testing.T) { + if got := routing.EscalationTier(routing.TierMedium, 0); got != routing.TierMedium { + t.Errorf("EscalationTier(medium, 0) = %q, want %q", got, routing.TierMedium) + } + if got := routing.EscalationTier(routing.TierMedium, 1); got != routing.TierHigh { + t.Errorf("EscalationTier(medium, 1) = %q, want %q", got, routing.TierHigh) + } +} + +func TestEscalationTierHighStaysHigh(t *testing.T) { + if got := routing.EscalationTier(routing.TierHigh, 0); got != routing.TierHigh { + t.Errorf("EscalationTier(high, 0) = %q, want %q", got, routing.TierHigh) + } + if got := routing.EscalationTier(routing.TierHigh, 1); got != routing.TierHigh { + t.Errorf("EscalationTier(high, 1) = %q, want %q", got, routing.TierHigh) + } +} diff --git a/internal/v2/stage/accept/accept.go b/internal/v2/stage/accept/accept.go index 8740bc4a8..247209164 100644 --- a/internal/v2/stage/accept/accept.go +++ b/internal/v2/stage/accept/accept.go @@ -243,7 +243,7 @@ func specFilePath(cfg *config.Config, root, specID string) (string, error) { func (s *Stage) buildPrompt(specID string, criterion coverage.Criterion, diff string) string { instance := s.buildInstanceLayer(specID, criterion, diff) assembler := v2prompt.NewPromptAssembler(s.baseLayer(), s.project, instance, s.fragment) - return assembler.Assemble() + return assembler.Assemble("", v2prompt.BeadInfo{}) } func (s *Stage) baseLayer() string { diff --git a/internal/v2/stage/build/build.go b/internal/v2/stage/build/build.go index 42be6ae2e..449c516f5 100644 --- a/internal/v2/stage/build/build.go +++ b/internal/v2/stage/build/build.go @@ -238,7 +238,7 @@ func (s *Stage) Run(ctx context.Context, req *stagepkg.Request) (*stagepkg.Resul methodology := s.resolveMethodology(req.Bead.Labels, cfg) fragment := s.fragmentFor(methodology) instance := buildInstanceLayer(req) - promptText := prompt.NewPromptAssembler(s.base, s.project, instance, fragment).Assemble() + promptText := prompt.NewPromptAssembler(s.base, s.project, instance, fragment).Assemble("", prompt.BeadInfo{}) model := s.selectModel(req, cfg) diff --git a/internal/v2/stage/plan/plan.go b/internal/v2/stage/plan/plan.go index ddc75d89b..5f96f6dcb 100644 --- a/internal/v2/stage/plan/plan.go +++ b/internal/v2/stage/plan/plan.go @@ -161,7 +161,7 @@ func (s *Stage) Run(ctx context.Context, req *stagepkg.Request) (*stagepkg.Resul } model := s.selectModel(req, cfg) - promptPayload := prompt.NewPromptAssembler(s.base, s.project, string(specData), s.fragment).Assemble() + promptPayload := prompt.NewPromptAssembler(s.base, s.project, string(specData), s.fragment).Assemble("", prompt.BeadInfo{}) resp, err := s.llm.Invoke(ctx, llmtypes.LLMInvokeRequest{Prompt: promptPayload, Model: model, Dir: req.Worktree}) if err != nil { return nil, fmt.Errorf("invoke llm: %w", err) diff --git a/internal/v2/stage/plan/plan_test.go b/internal/v2/stage/plan/plan_test.go index f5ec26ede..6089659ef 100644 --- a/internal/v2/stage/plan/plan_test.go +++ b/internal/v2/stage/plan/plan_test.go @@ -90,7 +90,7 @@ func TestStageWritesPlanAndInvokesLLM(t *testing.T) { t.Fatalf("plan file contents mismatch: %q", string(data)) } - expectedPrompt := prompt.NewPromptAssembler(baseLayer, projectLayer, specContent, fragmentLayer).Assemble() + expectedPrompt := prompt.NewPromptAssembler(baseLayer, projectLayer, specContent, fragmentLayer).Assemble("", prompt.BeadInfo{}) if fake.lastRequest.Prompt != expectedPrompt { t.Fatalf("prompt mismatch: got %q want %q", fake.lastRequest.Prompt, expectedPrompt) } @@ -158,7 +158,7 @@ func TestStageUsesLLMProvider(t *testing.T) { t.Fatalf("plan mismatch: got %q want %q", artifacts.Plan, provider.response.Output) } - expectedPrompt := prompt.NewPromptAssembler(baseLayer, projectLayer, specContent, fragmentLayer).Assemble() + expectedPrompt := prompt.NewPromptAssembler(baseLayer, projectLayer, specContent, fragmentLayer).Assemble("", prompt.BeadInfo{}) if provider.lastRequest.Prompt != expectedPrompt { t.Fatalf("prompt mismatch: got %q want %q", provider.lastRequest.Prompt, expectedPrompt) } diff --git a/internal/v2/stage/review/review.go b/internal/v2/stage/review/review.go index dc16d9a6e..789f6f3e1 100644 --- a/internal/v2/stage/review/review.go +++ b/internal/v2/stage/review/review.go @@ -204,7 +204,7 @@ func (s *Stage) Run(ctx context.Context, req *stagepkg.Request) (*stagepkg.Resul } instance := buildInstanceLayer(diff, acceptance) - promptText := prompt.NewPromptAssembler(s.base, s.project, instance, s.fragment).Assemble() + promptText := prompt.NewPromptAssembler(s.base, s.project, instance, s.fragment).Assemble("", prompt.BeadInfo{}) model, err := reviewModel(cfg) if err != nil { diff --git a/internal/v2/stage/stage.go b/internal/v2/stage/stage.go index 12d9bad1b..74d67f245 100644 --- a/internal/v2/stage/stage.go +++ b/internal/v2/stage/stage.go @@ -7,6 +7,7 @@ import ( "github.com/danabrams/gromit/internal/bead" "github.com/danabrams/gromit/internal/config" "github.com/danabrams/gromit/internal/v2/event" + "github.com/danabrams/gromit/internal/v2/llmtypes" ) // Stage defines a single beam within the run loop. @@ -19,6 +20,7 @@ type Stage interface { type StageRequest struct { Bead BeadInfo Model string + Provider llmtypes.LLMProvider Iteration int Config *config.Config Worktree string diff --git a/internal/v2/stage/triage/triage.go b/internal/v2/stage/triage/triage.go index c0e1b8dec..0effaccba 100644 --- a/internal/v2/stage/triage/triage.go +++ b/internal/v2/stage/triage/triage.go @@ -114,7 +114,7 @@ func (s *Stage) Run(ctx context.Context, req *stagepkg.Request) (*stagepkg.Resul } instance := buildTriageInstance(req.Bead.ID, req.Bead.Title, labels, failureMessage) - promptText := prompt.NewPromptAssembler(s.base, s.project, instance, s.fragment).Assemble() + promptText := prompt.NewPromptAssembler(s.base, s.project, instance, s.fragment).Assemble("", prompt.BeadInfo{}) model := s.cfg.Models.P2 if model == "" {