devrites 3.2.8 → 3.2.9
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/docs/orchestration.md +8 -0
- package/engine/hooks_agent_dispatch.go +3 -3
- package/engine/hooks_workspace.go +5 -2
- package/engine/tests/hook_test.go +21 -4
- 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.9](https://github.com/ViktorsBaikers/DevRites/compare/v3.2.8...v3.2.9) (2026-07-26)
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
* **devrites:** unblock retained reconcile checks ([c1093b0](https://github.com/ViktorsBaikers/DevRites/commit/c1093b00c3e32ba3cc22e0b9a2da548618ead621))
|
|
10
|
+
|
|
5
11
|
## [3.2.8](https://github.com/ViktorsBaikers/DevRites/compare/v3.2.7...v3.2.8) (2026-07-26)
|
|
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.9`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v3.2.9): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
|
|
29
29
|
|
|
30
30
|
## Quick start
|
|
31
31
|
|
package/docs/orchestration.md
CHANGED
|
@@ -69,6 +69,14 @@ Each dispatch follows the retained-baseline sequence:
|
|
|
69
69
|
5. After proof and decision checks pass, `reconcile close` retires the private
|
|
70
70
|
window; only then does the root write canonical records.
|
|
71
71
|
|
|
72
|
+
An ordinary `reconcile check` may revalidate an existing retained window
|
|
73
|
+
without a fresh current-turn wright receipt; it does not retire or widen that
|
|
74
|
+
window. `reconcile close` still requires the confirmed, awaited wright result
|
|
75
|
+
bound to the active snapshot. While the window is armed, the root guard permits
|
|
76
|
+
inspectable writes only under `.devrites` or at an absolute scratch path outside
|
|
77
|
+
the project; source-tree writes, relative escapes, and opaque execution remain
|
|
78
|
+
blocked.
|
|
79
|
+
|
|
72
80
|
The canonical-state fingerprint excludes root-owned operational records:
|
|
73
81
|
`timeline.jsonl`, feature `events.jsonl`, a valid `recovery-attempts.jsonl`,
|
|
74
82
|
the redwatch sentinel, automatic handoff snapshots, and hidden hook observation
|
|
@@ -33,7 +33,7 @@ var (
|
|
|
33
33
|
devritesAgentPathRe = regexp.MustCompile(`\.codex/agents/(devrites-[a-z0-9-]+)\.toml`)
|
|
34
34
|
devritesAgentNameRe = regexp.MustCompile(`^devrites-[a-z0-9-]+$`)
|
|
35
35
|
skillInvocationRe = regexp.MustCompile(`(?i)(?:^|[^a-z0-9_./-])(?:\$|/)([a-z][a-z0-9-]*)\b`)
|
|
36
|
-
|
|
36
|
+
reconcileCloseRe = regexp.MustCompile(`(?m)(?:^|[;&|]\s*)(?:rtk\s+)?(?:[A-Za-z0-9_./-]+/)?devrites-engine\s+reconcile\s+close\b`)
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
type agentDispatchHookInput struct {
|
|
@@ -1163,7 +1163,7 @@ func hookAgentDispatchPreTool(h harness.Harness, root string, in agentDispatchHo
|
|
|
1163
1163
|
return exitOK
|
|
1164
1164
|
}
|
|
1165
1165
|
|
|
1166
|
-
if isShellTool(in.ToolName) &&
|
|
1166
|
+
if isShellTool(in.ToolName) && reconcileCloseRe.MatchString(in.ToolInput.Command) {
|
|
1167
1167
|
windowID := currentReconcileWindowID()
|
|
1168
1168
|
if windowID == "" {
|
|
1169
1169
|
return exitOK
|
|
@@ -1177,7 +1177,7 @@ func hookAgentDispatchPreTool(h harness.Harness, root string, in agentDispatchHo
|
|
|
1177
1177
|
}
|
|
1178
1178
|
attempts = append(attempts, durable...)
|
|
1179
1179
|
if !roleSatisfied("devrites-slice-wright", windowID, attempts) {
|
|
1180
|
-
return preToolDeny(h, "DevRites reconcile
|
|
1180
|
+
return preToolDeny(h, "DevRites reconcile close requires a confirmed, awaited devrites-slice-wright result bound to the active reconcile snapshot.", stdout, stderr)
|
|
1181
1181
|
}
|
|
1182
1182
|
for _, attempt := range durable {
|
|
1183
1183
|
if attempt.Role != "devrites-slice-wright" || attempt.WindowID != windowID ||
|
|
@@ -1024,8 +1024,9 @@ func guardRootBuildWindow(h harness.Harness, data []byte, in harness.GuardInput,
|
|
|
1024
1024
|
}
|
|
1025
1025
|
targets, suspicious := rootMutationTargets(data, in)
|
|
1026
1026
|
for _, target := range targets {
|
|
1027
|
+
absoluteTarget := filepath.IsAbs(target)
|
|
1027
1028
|
abs := target
|
|
1028
|
-
if !
|
|
1029
|
+
if !absoluteTarget {
|
|
1029
1030
|
abs = filepath.Join(projectDir, abs)
|
|
1030
1031
|
}
|
|
1031
1032
|
abs = filepath.Clean(abs)
|
|
@@ -1033,7 +1034,9 @@ func guardRootBuildWindow(h harness.Harness, data []byte, in harness.GuardInput,
|
|
|
1033
1034
|
if err != nil {
|
|
1034
1035
|
return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
|
|
1035
1036
|
}
|
|
1036
|
-
|
|
1037
|
+
insideProject := pathWithin(resolved, projectDir)
|
|
1038
|
+
if (insideProject && !underDevrites(resolved, root)) ||
|
|
1039
|
+
(!insideProject && !absoluteTarget) {
|
|
1037
1040
|
return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
|
|
1038
1041
|
}
|
|
1039
1042
|
}
|
|
@@ -583,6 +583,11 @@ func TestHookWrightScopeRequiresSpawnedCodexWorkerForSourceWrites(t *testing.T)
|
|
|
583
583
|
runGuard(`{"tool_name":"spawn_agent","tool_input":{"agent_type":"worker"}}`, false)
|
|
584
584
|
runGuard(`{"tool_name":"spawn_agent","tool_input":{"agent_type":"explorer"}}`, false)
|
|
585
585
|
runGuard(`{"tool_name":"Write","tool_input":{"file_path":".devrites/features/auth-tokens/state.md"}}`, false)
|
|
586
|
+
runGuard(hookPayload(t, map[string]any{
|
|
587
|
+
"tool_name": "Write",
|
|
588
|
+
"tool_input": map[string]any{"file_path": filepath.Join(t.TempDir(), "agent-packet.yaml")},
|
|
589
|
+
}), false)
|
|
590
|
+
runGuard(`{"tool_name":"Write","tool_input":{"file_path":"../outside.txt"}}`, true)
|
|
586
591
|
runGuard(`{"tool_name":"Write","tool_input":{"file_path":"src/app.go"}}`, true)
|
|
587
592
|
runGuard(`{"tool_name":"Bash","tool_input":{"command":"printf x > src/app.go"}}`, true)
|
|
588
593
|
runGuard(`{"tool_name":"js","tool_input":{"code":"writeFile()"}}`, true)
|
|
@@ -1695,7 +1700,7 @@ func TestCodexAgentDispatchRejectsIncompleteDurableV2(t *testing.T) {
|
|
|
1695
1700
|
}
|
|
1696
1701
|
}
|
|
1697
1702
|
|
|
1698
|
-
func
|
|
1703
|
+
func TestCodexAgentDispatchGatesReconcileCloseOnBoundWrightResult(t *testing.T) {
|
|
1699
1704
|
root := newWorkspace(t)
|
|
1700
1705
|
writeActive(t, root, "auth-tokens")
|
|
1701
1706
|
workspace := filepath.Join(root, "features", "auth-tokens")
|
|
@@ -1714,14 +1719,26 @@ func TestCodexAgentDispatchGatesReconcileOnBoundWrightResult(t *testing.T) {
|
|
|
1714
1719
|
"prompt": "$rite-build",
|
|
1715
1720
|
}), nil, "hook", "agent-dispatch", "--harness=codex")
|
|
1716
1721
|
|
|
1717
|
-
|
|
1722
|
+
check := hookPayload(t, map[string]any{
|
|
1723
|
+
"hook_event_name": "PreToolUse",
|
|
1724
|
+
"session_id": sessionID,
|
|
1725
|
+
"turn_id": turnID,
|
|
1726
|
+
"tool_name": "Bash",
|
|
1727
|
+
"tool_input": map[string]any{"command": "rtk devrites-engine reconcile check"},
|
|
1728
|
+
})
|
|
1729
|
+
if out, stderr, code := runDevritesIO(t, root, check, nil,
|
|
1730
|
+
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1731
|
+
t.Fatalf("ordinary reconcile check exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
closeWindow := hookPayload(t, map[string]any{
|
|
1718
1735
|
"hook_event_name": "PreToolUse",
|
|
1719
1736
|
"session_id": sessionID,
|
|
1720
1737
|
"turn_id": turnID,
|
|
1721
1738
|
"tool_name": "Bash",
|
|
1722
1739
|
"tool_input": map[string]any{"command": "rtk devrites-engine reconcile close"},
|
|
1723
1740
|
})
|
|
1724
|
-
out, _, _ := runDevritesIO(t, root,
|
|
1741
|
+
out, _, _ := runDevritesIO(t, root, closeWindow, nil,
|
|
1725
1742
|
"hook", "agent-dispatch", "--harness=codex")
|
|
1726
1743
|
if decision, reason := parsePermissionDecision(t, out); decision != "deny" ||
|
|
1727
1744
|
!strings.Contains(reason, "confirmed") {
|
|
@@ -1774,7 +1791,7 @@ func TestCodexAgentDispatchGatesReconcileOnBoundWrightResult(t *testing.T) {
|
|
|
1774
1791
|
}
|
|
1775
1792
|
}
|
|
1776
1793
|
|
|
1777
|
-
if out, stderr, code := runDevritesIO(t, root,
|
|
1794
|
+
if out, stderr, code := runDevritesIO(t, root, closeWindow, nil,
|
|
1778
1795
|
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1779
1796
|
t.Fatalf("completed wright reconcile exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1780
1797
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devrites",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.9",
|
|
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",
|