devrites 3.2.4 → 3.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to DevRites are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and DevRites adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are generated automatically by [semantic-release](https://semantic-release.gitbook.io/) from Conventional Commits on `main`.
4
4
 
5
+ ## [3.2.5](https://github.com/ViktorsBaikers/DevRites/compare/v3.2.4...v3.2.5) (2026-07-25)
6
+
7
+ ### Fixed
8
+
9
+ * **agents:** retain v2 wright dispatch receipt ([84f0c05](https://github.com/ViktorsBaikers/DevRites/commit/84f0c05c9d91d85194dfd4c97593ca1c07183282))
10
+
5
11
  ## [3.2.4](https://github.com/ViktorsBaikers/DevRites/compare/v3.2.3...v3.2.4) (2026-07-25)
6
12
 
7
13
  ### Fixed
package/README.md CHANGED
@@ -25,7 +25,7 @@ final commit, push, and tag, and it requires a typed `GO` confirmation.
25
25
  Unattended runs may create local WIP checkpoint commits along the way, but only
26
26
  Ship collapses and pushes them.
27
27
 
28
- **Status:** [`v3.2.4`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v3.2.4): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
28
+ **Status:** [`v3.2.5`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v3.2.5): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
29
29
 
30
30
  ## Quick start
31
31
 
@@ -457,6 +457,17 @@ func dispatchTurnState(events []agentDispatchEvent, turnID string) (map[string]s
457
457
  attempt.Stopped = true
458
458
  attempt.ResultSHA256 = event.ResultSHA256
459
459
  }
460
+ case "verified":
461
+ attempts = append(attempts, &agentDispatchAttempt{
462
+ Role: event.Role,
463
+ AgentType: event.AgentType,
464
+ AgentID: event.AgentID,
465
+ WindowID: event.WindowID,
466
+ Started: true,
467
+ Stopped: true,
468
+ Waited: true,
469
+ ResultSHA256: event.ResultSHA256,
470
+ })
460
471
  }
461
472
  }
462
473
  return armed, attempts
@@ -771,7 +782,7 @@ func reconcileWindowPredates(at time.Time) bool {
771
782
  }
772
783
 
773
784
  func dispatchAttemptComplete(attempt *agentDispatchAttempt) bool {
774
- return attempt.Started && attempt.Stopped && attempt.ResultSHA256 != ""
785
+ return attempt.Started && attempt.Stopped && attempt.Waited && attempt.ResultSHA256 != ""
775
786
  }
776
787
 
777
788
  func roleSatisfied(role, windowID string, attempts []*agentDispatchAttempt) bool {
@@ -1092,15 +1103,34 @@ func hookAgentDispatchPreTool(h harness.Harness, root string, in agentDispatchHo
1092
1103
  if windowID == "" {
1093
1104
  return exitOK
1094
1105
  }
1095
- durable, durableErr := durableCodexV2DispatchAttempts(
1096
- root, in.SessionID, in.TurnID, armed,
1097
- )
1098
- if durableErr != nil {
1099
- return preToolDeny(h, "DevRites could not verify the Codex V2 wright result: "+durableErr.Error(), stdout, stderr)
1100
- }
1101
- attempts = append(attempts, durable...)
1102
1106
  if !roleSatisfied("devrites-slice-wright", windowID, attempts) {
1103
- return preToolDeny(h, "DevRites reconcile check/close requires a confirmed, awaited devrites-slice-wright result bound to the active reconcile snapshot.", stdout, stderr)
1107
+ durable, durableErr := durableCodexV2DispatchAttempts(
1108
+ root, in.SessionID, in.TurnID, armed,
1109
+ )
1110
+ if durableErr != nil {
1111
+ return preToolDeny(h, "DevRites could not verify the Codex V2 wright result: "+durableErr.Error(), stdout, stderr)
1112
+ }
1113
+ attempts = append(attempts, durable...)
1114
+ if !roleSatisfied("devrites-slice-wright", windowID, attempts) {
1115
+ return preToolDeny(h, "DevRites reconcile check/close requires a confirmed, awaited devrites-slice-wright result bound to the active reconcile snapshot.", stdout, stderr)
1116
+ }
1117
+ for _, attempt := range durable {
1118
+ if attempt.Role != "devrites-slice-wright" || attempt.WindowID != windowID ||
1119
+ !dispatchAttemptComplete(attempt) {
1120
+ continue
1121
+ }
1122
+ if err := appendAgentDispatchEvent(root, in.SessionID, agentDispatchEvent{
1123
+ Event: "verified",
1124
+ TurnID: in.TurnID,
1125
+ AgentID: attempt.AgentID,
1126
+ AgentType: attempt.AgentType,
1127
+ Role: attempt.Role,
1128
+ WindowID: attempt.WindowID,
1129
+ ResultSHA256: attempt.ResultSHA256,
1130
+ }); err != nil {
1131
+ return preToolDeny(h, "DevRites could not persist the verified Codex V2 wright result: "+err.Error(), stdout, stderr)
1132
+ }
1133
+ }
1104
1134
  }
1105
1135
  }
1106
1136
  return exitOK
@@ -1341,6 +1341,33 @@ func TestCodexAgentDispatchConfirmsGenericRoleAndResult(t *testing.T) {
1341
1341
  "turn_id": turnID,
1342
1342
  "stop_hook_active": false,
1343
1343
  })
1344
+ out, stderr, code = runDevritesIO(t, root, stop, nil,
1345
+ "hook", "agent-dispatch", "--harness=codex")
1346
+ if code != 0 {
1347
+ t.Fatalf("unawaited stop exit=%d stderr=%s", code, stderr)
1348
+ }
1349
+ var blocked struct {
1350
+ Decision string `json:"decision"`
1351
+ }
1352
+ if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &blocked); err != nil {
1353
+ t.Fatalf("invalid unawaited Stop response: %v\n%s", err, out)
1354
+ }
1355
+ if blocked.Decision != "block" {
1356
+ t.Fatalf("unawaited child result passed Stop: %s", out)
1357
+ }
1358
+
1359
+ wait := hookPayload(t, map[string]any{
1360
+ "hook_event_name": "PreToolUse",
1361
+ "session_id": sessionID,
1362
+ "turn_id": turnID,
1363
+ "tool_name": "wait",
1364
+ "tool_input": map[string]any{"ids": []string{"agent-1"}},
1365
+ })
1366
+ if out, stderr, code := runDevritesIO(t, root, wait, nil,
1367
+ "hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
1368
+ t.Fatalf("wait exit=%d out=%s stderr=%s", code, out, stderr)
1369
+ }
1370
+
1344
1371
  if out, stderr, code := runDevritesIO(t, root, stop, nil,
1345
1372
  "hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
1346
1373
  t.Fatalf("resolved stop exit=%d out=%s stderr=%s", code, out, stderr)
@@ -1579,6 +1606,21 @@ func TestCodexAgentDispatchBindsDurableV2WrightToReconcileWindow(t *testing.T) {
1579
1606
  if decision, _ := parsePermissionDecision(t, out); decision != "deny" {
1580
1607
  t.Fatalf("changed-window V2 wright was accepted: %s", out)
1581
1608
  }
1609
+ return
1610
+ }
1611
+ for _, name := range []string{".reconcile-base", ".reconcile-allowlist", ".reconcile-devrites"} {
1612
+ if err := os.Remove(filepath.Join(workspace, name)); err != nil {
1613
+ t.Fatal(err)
1614
+ }
1615
+ }
1616
+ stop := hookPayload(t, map[string]any{
1617
+ "hook_event_name": "Stop",
1618
+ "session_id": sessionID,
1619
+ "turn_id": turnID,
1620
+ })
1621
+ if out, stderr, code := runDevritesIO(t, root, stop, nil,
1622
+ "hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
1623
+ t.Fatalf("persisted V2 wright receipt was lost after close: exit=%d out=%s stderr=%s", code, out, stderr)
1582
1624
  }
1583
1625
  })
1584
1626
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devrites",
3
- "version": "3.2.4",
3
+ "version": "3.2.5",
4
4
  "description": "DevRites: a disciplined senior-engineer workflow pack for Claude Code and Codex",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://github.com/ViktorsBaikers/DevRites#readme",