devrites 3.2.21 → 3.2.22
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 +6 -0
- package/README.md +1 -1
- package/engine/hooks_agent_dispatch.go +11 -2
- package/engine/tests/hook_test.go +15 -3
- package/package.json +1 -1
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.22](https://github.com/ViktorsBaikers/DevRites/compare/v3.2.21...v3.2.22) (2026-07-28)
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
* **devrites:** prevent receipt-only agent spawns ([4cf4c19](https://github.com/ViktorsBaikers/DevRites/commit/4cf4c19cf8671b8fdd497b8875c50bd50faf85d9))
|
|
10
|
+
|
|
5
11
|
## [3.2.21](https://github.com/ViktorsBaikers/DevRites/compare/v3.2.20...v3.2.21) (2026-07-28)
|
|
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.
|
|
28
|
+
**Status:** [`v3.2.22`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v3.2.22): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
|
|
29
29
|
|
|
30
30
|
## Quick start
|
|
31
31
|
|
|
@@ -853,6 +853,10 @@ func conditionalDispatchInstruction() string {
|
|
|
853
853
|
return "CONDITIONAL DISPATCH RULE — If this skill reaches a child-agent step, call spawn_agent with the exact named agent_type=devrites-<role> specified by the skill, a unique task_name, and fork_turns=\"none\". GPT-5.6 V2 may omit agent_type from the visible tool schema; send agent_type anyway and never use a default child. Wait for the returned child and use its non-empty result."
|
|
854
854
|
}
|
|
855
855
|
|
|
856
|
+
func preDispatchStopInstruction() string {
|
|
857
|
+
return "PRE-DISPATCH STOP RULE — If a deterministic prerequisite forces STOP before any spawn attempt, do not spawn only to satisfy this receipt. Run exactly one successful standalone devrites-engine dispatch-waive <reason> before replying. Use only the matching observed predicate: no-active-workspace, wrong-phase, readiness-failed, no-eligible-work, or human-gate-before-dispatch. The successful PostToolUse receipt is required; never waive after a spawn attempt."
|
|
858
|
+
}
|
|
859
|
+
|
|
856
860
|
func incompleteDispatchReason(armed map[string]struct{}, attempts []*agentDispatchAttempt) string {
|
|
857
861
|
roles := make([]string, 0, len(armed))
|
|
858
862
|
for role := range armed {
|
|
@@ -871,7 +875,11 @@ func incompleteDispatchReason(armed map[string]struct{}, attempts []*agentDispat
|
|
|
871
875
|
if role == agentDispatchMetadataRole {
|
|
872
876
|
return "DevRites could not determine this skill's required agents because its installed required-agent-roles contract is invalid. Reinstall or repair the DevRites skill pack before continuing."
|
|
873
877
|
}
|
|
874
|
-
|
|
878
|
+
reason := fmt.Sprintf("DevRites dispatch for %s is not complete. %s", role, dispatchInstruction(role))
|
|
879
|
+
if len(attempts) == 0 {
|
|
880
|
+
reason += " " + preDispatchStopInstruction()
|
|
881
|
+
}
|
|
882
|
+
return reason
|
|
875
883
|
}
|
|
876
884
|
|
|
877
885
|
func preToolDeny(h harness.Harness, reason string, stdout, stderr io.Writer) int {
|
|
@@ -1022,8 +1030,9 @@ func hookAgentDispatch(h harness.Harness, stdin io.Reader, stdout, stderr io.Wri
|
|
|
1022
1030
|
}
|
|
1023
1031
|
fmt.Fprintf(
|
|
1024
1032
|
stdout,
|
|
1025
|
-
"MANDATORY DISPATCH THIS TURN — At the skill's dispatch step, execute every required child before finishing or claiming completion; a success phrase is not evidence.\n%s",
|
|
1033
|
+
"MANDATORY DISPATCH THIS TURN — At the skill's dispatch step, execute every required child before finishing or claiming completion; a success phrase is not evidence.\n%s\n%s",
|
|
1026
1034
|
strings.Join(instructions, "\n"),
|
|
1035
|
+
preDispatchStopInstruction(),
|
|
1027
1036
|
)
|
|
1028
1037
|
}
|
|
1029
1038
|
if skillInvoked {
|
|
@@ -1441,6 +1441,12 @@ func TestCodexAgentDispatchPromptGivesExactNamedCallBeforeCompletion(t *testing.
|
|
|
1441
1441
|
".codex/agents/" + role + ".toml",
|
|
1442
1442
|
"send agent_type anyway",
|
|
1443
1443
|
"Wait for the returned child",
|
|
1444
|
+
"PRE-DISPATCH STOP RULE",
|
|
1445
|
+
"do not spawn only to satisfy this receipt",
|
|
1446
|
+
"devrites-engine dispatch-waive <reason>",
|
|
1447
|
+
"readiness-failed",
|
|
1448
|
+
"human-gate-before-dispatch",
|
|
1449
|
+
"never waive after a spawn attempt",
|
|
1444
1450
|
} {
|
|
1445
1451
|
if !strings.Contains(out, want) {
|
|
1446
1452
|
t.Fatalf("initial dispatch guidance omitted %q:\n%s", want, out)
|
|
@@ -1692,7 +1698,9 @@ func TestCodexAgentDispatchBlocksFalseWaitAndStop(t *testing.T) {
|
|
|
1692
1698
|
if stopDecision.Decision != "block" ||
|
|
1693
1699
|
!strings.Contains(stopDecision.Reason, "spawn_agent") ||
|
|
1694
1700
|
!strings.Contains(stopDecision.Reason, "visible tool schema") ||
|
|
1695
|
-
!strings.Contains(stopDecision.Reason, "send agent_type anyway")
|
|
1701
|
+
!strings.Contains(stopDecision.Reason, "send agent_type anyway") ||
|
|
1702
|
+
!strings.Contains(stopDecision.Reason, "do not spawn only to satisfy this receipt") ||
|
|
1703
|
+
!strings.Contains(stopDecision.Reason, "dispatch-waive <reason>") {
|
|
1696
1704
|
t.Fatalf("false completion not blocked: %#v", stopDecision)
|
|
1697
1705
|
}
|
|
1698
1706
|
|
|
@@ -1745,7 +1753,7 @@ func TestCodexAgentDispatchRequiresSuccessfulReasonBoundWaiver(t *testing.T) {
|
|
|
1745
1753
|
t.Fatalf("chained waiver was not denied: %s", out)
|
|
1746
1754
|
}
|
|
1747
1755
|
|
|
1748
|
-
command := "devrites-engine dispatch-waive
|
|
1756
|
+
command := "devrites-engine dispatch-waive readiness-failed"
|
|
1749
1757
|
pre := hookPayload(t, map[string]any{
|
|
1750
1758
|
"hook_event_name": "PreToolUse",
|
|
1751
1759
|
"session_id": sessionID,
|
|
@@ -1775,7 +1783,7 @@ func TestCodexAgentDispatchRequiresSuccessfulReasonBoundWaiver(t *testing.T) {
|
|
|
1775
1783
|
"turn_id": turnID,
|
|
1776
1784
|
"tool_name": "functions.exec_command",
|
|
1777
1785
|
"tool_input": map[string]any{"cmd": command},
|
|
1778
|
-
"tool_response": "dispatch-waive: accepted
|
|
1786
|
+
"tool_response": "dispatch-waive: accepted readiness-failed",
|
|
1779
1787
|
})
|
|
1780
1788
|
if out, stderr, code := runDevritesIO(t, root, post, nil,
|
|
1781
1789
|
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
@@ -1887,6 +1895,7 @@ func TestCodexAgentDispatchConfirmsGenericRoleAndResult(t *testing.T) {
|
|
|
1887
1895
|
}
|
|
1888
1896
|
var blocked struct {
|
|
1889
1897
|
Decision string `json:"decision"`
|
|
1898
|
+
Reason string `json:"reason"`
|
|
1890
1899
|
}
|
|
1891
1900
|
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &blocked); err != nil {
|
|
1892
1901
|
t.Fatalf("invalid unawaited Stop response: %v\n%s", err, out)
|
|
@@ -1894,6 +1903,9 @@ func TestCodexAgentDispatchConfirmsGenericRoleAndResult(t *testing.T) {
|
|
|
1894
1903
|
if blocked.Decision != "block" {
|
|
1895
1904
|
t.Fatalf("unawaited child result passed Stop: %s", out)
|
|
1896
1905
|
}
|
|
1906
|
+
if strings.Contains(blocked.Reason, "dispatch-waive") {
|
|
1907
|
+
t.Fatalf("unavailable post-spawn waiver was suggested: %s", out)
|
|
1908
|
+
}
|
|
1897
1909
|
|
|
1898
1910
|
wait := hookPayload(t, map[string]any{
|
|
1899
1911
|
"hook_event_name": "PreToolUse",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devrites",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.22",
|
|
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",
|