devrites 3.2.2 → 3.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/docs/architecture.md +6 -2
  4. package/docs/command-map.md +3 -3
  5. package/docs/engine/agent-contract.md +3 -2
  6. package/docs/harness-compliance.md +2 -2
  7. package/docs/orchestration.md +15 -12
  8. package/docs/porting-to-a-new-harness.md +2 -1
  9. package/docs/skills.md +7 -2
  10. package/engine/hooks.go +39 -4
  11. package/engine/hooks_agent_dispatch.go +1193 -0
  12. package/engine/hooks_workspace.go +78 -10
  13. package/engine/internal/forge/forge.go +0 -2
  14. package/engine/internal/harness/compliance.go +1 -1
  15. package/engine/internal/harness/harness.go +3 -0
  16. package/engine/internal/lib/doubtcoverage.go +2 -12
  17. package/engine/internal/lib/reconcile.go +4 -29
  18. package/engine/internal/lib/reconcile_test.go +14 -10
  19. package/engine/main.go +4 -1
  20. package/engine/tests/hook_test.go +877 -1
  21. package/engine/tests/lib_parity_test.go +5 -17
  22. package/engine/tests/parity_reconcile_test.go +1 -1
  23. package/engine/tests/parity_test.go +2 -6
  24. package/pack/.claude/skills/devrites-api-interface/SKILL.md +1 -0
  25. package/pack/.claude/skills/devrites-audit/SKILL.md +5 -6
  26. package/pack/.claude/skills/devrites-browser-proof/SKILL.md +1 -0
  27. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +1 -0
  28. package/pack/.claude/skills/devrites-doubt/SKILL.md +2 -1
  29. package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +1 -0
  30. package/pack/.claude/skills/devrites-interview/SKILL.md +1 -0
  31. package/pack/.claude/skills/devrites-lib/SKILL.md +1 -0
  32. package/pack/.claude/skills/devrites-lib/reference/model-tiers.md +4 -3
  33. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +4 -5
  34. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +15 -8
  35. package/pack/.claude/skills/devrites-prose-craft/SKILL.md +1 -0
  36. package/pack/.claude/skills/devrites-refresh-indexes/SKILL.md +1 -0
  37. package/pack/.claude/skills/devrites-source-driven/SKILL.md +1 -0
  38. package/pack/.claude/skills/devrites-ux-shape/SKILL.md +1 -0
  39. package/pack/.claude/skills/rite/SKILL.md +1 -0
  40. package/pack/.claude/skills/rite-adopt/SKILL.md +1 -0
  41. package/pack/.claude/skills/rite-autocomplete/SKILL.md +1 -0
  42. package/pack/.claude/skills/rite-build/SKILL.md +2 -1
  43. package/pack/.claude/skills/rite-build/reference/phase-contract.md +3 -4
  44. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +11 -12
  45. package/pack/.claude/skills/rite-clarify/SKILL.md +1 -0
  46. package/pack/.claude/skills/rite-converge/SKILL.md +1 -0
  47. package/pack/.claude/skills/rite-customize/SKILL.md +1 -0
  48. package/pack/.claude/skills/rite-define/SKILL.md +1 -0
  49. package/pack/.claude/skills/rite-doctor/SKILL.md +1 -0
  50. package/pack/.claude/skills/rite-dogfood/SKILL.md +1 -0
  51. package/pack/.claude/skills/rite-explain/SKILL.md +4 -2
  52. package/pack/.claude/skills/rite-frame/SKILL.md +1 -0
  53. package/pack/.claude/skills/rite-handoff/SKILL.md +1 -0
  54. package/pack/.claude/skills/rite-learn/SKILL.md +1 -0
  55. package/pack/.claude/skills/rite-plan/SKILL.md +1 -0
  56. package/pack/.claude/skills/rite-polish/SKILL.md +1 -0
  57. package/pack/.claude/skills/rite-pov/SKILL.md +1 -0
  58. package/pack/.claude/skills/rite-pr-feedback/SKILL.md +1 -0
  59. package/pack/.claude/skills/rite-pressure-test/SKILL.md +1 -0
  60. package/pack/.claude/skills/rite-prototype/SKILL.md +1 -0
  61. package/pack/.claude/skills/rite-prove/SKILL.md +1 -0
  62. package/pack/.claude/skills/rite-quick/SKILL.md +1 -0
  63. package/pack/.claude/skills/rite-resolve/SKILL.md +1 -0
  64. package/pack/.claude/skills/rite-review/SKILL.md +1 -0
  65. package/pack/.claude/skills/rite-seal/SKILL.md +1 -0
  66. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +2 -3
  67. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +10 -12
  68. package/pack/.claude/skills/rite-ship/SKILL.md +1 -0
  69. package/pack/.claude/skills/rite-spec/SKILL.md +1 -0
  70. package/pack/.claude/skills/rite-status/SKILL.md +1 -0
  71. package/pack/.claude/skills/rite-temper/SKILL.md +3 -2
  72. package/pack/.claude/skills/rite-upgrade/SKILL.md +3 -1
  73. package/pack/.claude/skills/rite-vet/SKILL.md +1 -0
  74. package/pack/.claude/skills/rite-zoom-out/SKILL.md +1 -0
  75. package/pack/generated/claude/skills/devrites-api-interface/SKILL.md +1 -0
  76. package/pack/generated/claude/skills/devrites-audit/SKILL.md +5 -6
  77. package/pack/generated/claude/skills/devrites-browser-proof/SKILL.md +1 -0
  78. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +1 -0
  79. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +2 -1
  80. package/pack/generated/claude/skills/devrites-frontend-craft/SKILL.md +1 -0
  81. package/pack/generated/claude/skills/devrites-interview/SKILL.md +1 -0
  82. package/pack/generated/claude/skills/devrites-lib/SKILL.md +1 -0
  83. package/pack/generated/claude/skills/devrites-lib/reference/model-tiers.md +4 -3
  84. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +4 -5
  85. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +15 -8
  86. package/pack/generated/claude/skills/devrites-prose-craft/SKILL.md +1 -0
  87. package/pack/generated/claude/skills/devrites-refresh-indexes/SKILL.md +1 -0
  88. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +1 -0
  89. package/pack/generated/claude/skills/devrites-ux-shape/SKILL.md +1 -0
  90. package/pack/generated/claude/skills/rite/SKILL.md +1 -0
  91. package/pack/generated/claude/skills/rite-adopt/SKILL.md +1 -0
  92. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +1 -0
  93. package/pack/generated/claude/skills/rite-build/SKILL.md +2 -1
  94. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +3 -4
  95. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +11 -12
  96. package/pack/generated/claude/skills/rite-clarify/SKILL.md +1 -0
  97. package/pack/generated/claude/skills/rite-converge/SKILL.md +1 -0
  98. package/pack/generated/claude/skills/rite-customize/SKILL.md +1 -0
  99. package/pack/generated/claude/skills/rite-define/SKILL.md +1 -0
  100. package/pack/generated/claude/skills/rite-doctor/SKILL.md +1 -0
  101. package/pack/generated/claude/skills/rite-dogfood/SKILL.md +1 -0
  102. package/pack/generated/claude/skills/rite-explain/SKILL.md +4 -2
  103. package/pack/generated/claude/skills/rite-frame/SKILL.md +1 -0
  104. package/pack/generated/claude/skills/rite-handoff/SKILL.md +1 -0
  105. package/pack/generated/claude/skills/rite-learn/SKILL.md +1 -0
  106. package/pack/generated/claude/skills/rite-plan/SKILL.md +1 -0
  107. package/pack/generated/claude/skills/rite-polish/SKILL.md +1 -0
  108. package/pack/generated/claude/skills/rite-pov/SKILL.md +1 -0
  109. package/pack/generated/claude/skills/rite-pr-feedback/SKILL.md +1 -0
  110. package/pack/generated/claude/skills/rite-pressure-test/SKILL.md +1 -0
  111. package/pack/generated/claude/skills/rite-prototype/SKILL.md +1 -0
  112. package/pack/generated/claude/skills/rite-prove/SKILL.md +1 -0
  113. package/pack/generated/claude/skills/rite-quick/SKILL.md +1 -0
  114. package/pack/generated/claude/skills/rite-resolve/SKILL.md +1 -0
  115. package/pack/generated/claude/skills/rite-review/SKILL.md +1 -0
  116. package/pack/generated/claude/skills/rite-seal/SKILL.md +1 -0
  117. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +2 -3
  118. package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +10 -12
  119. package/pack/generated/claude/skills/rite-ship/SKILL.md +1 -0
  120. package/pack/generated/claude/skills/rite-spec/SKILL.md +1 -0
  121. package/pack/generated/claude/skills/rite-status/SKILL.md +1 -0
  122. package/pack/generated/claude/skills/rite-temper/SKILL.md +3 -2
  123. package/pack/generated/claude/skills/rite-upgrade/SKILL.md +3 -1
  124. package/pack/generated/claude/skills/rite-vet/SKILL.md +1 -0
  125. package/pack/generated/claude/skills/rite-zoom-out/SKILL.md +1 -0
  126. package/pack/generated/codex/AGENTS.md +2 -4
  127. package/pack/generated/codex/hooks.json +24 -5
  128. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +6 -4
  129. package/pack/generated/codex/skills/devrites-audit/SKILL.md +10 -10
  130. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +6 -4
  131. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +6 -4
  132. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +7 -5
  133. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +6 -4
  134. package/pack/generated/codex/skills/devrites-interview/SKILL.md +6 -4
  135. package/pack/generated/codex/skills/devrites-lib/SKILL.md +6 -4
  136. package/pack/generated/codex/skills/devrites-lib/reference/model-tiers.md +4 -3
  137. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +4 -5
  138. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +15 -8
  139. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +6 -4
  140. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +6 -4
  141. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +6 -4
  142. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +6 -4
  143. package/pack/generated/codex/skills/rite/SKILL.md +6 -4
  144. package/pack/generated/codex/skills/rite-adopt/SKILL.md +6 -4
  145. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +6 -4
  146. package/pack/generated/codex/skills/rite-build/SKILL.md +7 -5
  147. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +3 -4
  148. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +11 -12
  149. package/pack/generated/codex/skills/rite-clarify/SKILL.md +6 -4
  150. package/pack/generated/codex/skills/rite-converge/SKILL.md +6 -4
  151. package/pack/generated/codex/skills/rite-customize/SKILL.md +6 -4
  152. package/pack/generated/codex/skills/rite-define/SKILL.md +6 -4
  153. package/pack/generated/codex/skills/rite-doctor/SKILL.md +6 -4
  154. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +6 -4
  155. package/pack/generated/codex/skills/rite-explain/SKILL.md +9 -6
  156. package/pack/generated/codex/skills/rite-frame/SKILL.md +6 -4
  157. package/pack/generated/codex/skills/rite-handoff/SKILL.md +6 -4
  158. package/pack/generated/codex/skills/rite-learn/SKILL.md +6 -4
  159. package/pack/generated/codex/skills/rite-plan/SKILL.md +6 -4
  160. package/pack/generated/codex/skills/rite-polish/SKILL.md +6 -4
  161. package/pack/generated/codex/skills/rite-pov/SKILL.md +6 -4
  162. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +6 -4
  163. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +6 -4
  164. package/pack/generated/codex/skills/rite-prototype/SKILL.md +6 -4
  165. package/pack/generated/codex/skills/rite-prove/SKILL.md +6 -4
  166. package/pack/generated/codex/skills/rite-quick/SKILL.md +6 -4
  167. package/pack/generated/codex/skills/rite-resolve/SKILL.md +6 -4
  168. package/pack/generated/codex/skills/rite-review/SKILL.md +6 -4
  169. package/pack/generated/codex/skills/rite-seal/SKILL.md +6 -4
  170. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +2 -3
  171. package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +10 -12
  172. package/pack/generated/codex/skills/rite-ship/SKILL.md +6 -4
  173. package/pack/generated/codex/skills/rite-spec/SKILL.md +6 -4
  174. package/pack/generated/codex/skills/rite-status/SKILL.md +6 -4
  175. package/pack/generated/codex/skills/rite-temper/SKILL.md +8 -6
  176. package/pack/generated/codex/skills/rite-upgrade/SKILL.md +8 -5
  177. package/pack/generated/codex/skills/rite-vet/SKILL.md +6 -4
  178. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +6 -4
  179. package/package.json +1 -1
  180. package/scripts/codex-generate.sh +32 -18
  181. package/scripts/live-hosts/agent-result.schema.json +2 -1
  182. package/scripts/live-hosts/claude.sh +2 -2
  183. package/scripts/live-hosts/codex.sh +4 -0
  184. package/scripts/live-hosts/fake-host.py +1 -8
  185. package/scripts/live-hosts/host-transport.py +1 -2
  186. package/scripts/run-agent-contract-evals.py +3 -3
  187. package/scripts/run-evals.sh +3 -34
  188. package/scripts/validate-frontmatter.py +34 -1
  189. package/engine/testdata/golden/TestParityA1Guard/inline-fallback-silent.golden +0 -1
  190. package/engine/testdata/golden/TestParityDoubtCoverage/arg=inline.golden +0 -3
  191. package/engine/testdata/golden/TestParityDoubtCoverage/arg=missing-inline.golden +0 -3
  192. package/scripts/eval-runner.py +0 -275
  193. package/scripts/eval-scorer.mjs +0 -46
  194. package/scripts/render-eval-summary.py +0 -48
  195. /package/engine/testdata/golden/TestParityReconcile/{inline-fallback → close-window}/snapshot.golden +0 -0
  196. /package/engine/testdata/golden/TestParityReconcile/{inline-fallback.golden → close-window.golden} +0 -0
@@ -465,14 +465,10 @@ func hookA1Guard(h harness.Harness, stdin io.Reader, stdout, stderr io.Writer) i
465
465
  if !ok {
466
466
  return exitOK
467
467
  }
468
- // The guard is active between a reconcile snapshot and its check. Inline
469
- // fallback makes the orchestrator the expected writer.
468
+ // The guard is active between a reconcile snapshot and its check.
470
469
  if !wsIsFile(filepath.Join(dir, ".reconcile-base")) {
471
470
  return exitOK
472
471
  }
473
- if wsIsFile(filepath.Join(dir, ".reconcile-inline")) {
474
- return exitOK
475
- }
476
472
 
477
473
  in := h.ParseGuardInput(bytes.NewReader(data))
478
474
  if !isEditTool(in.ToolName) {
@@ -562,9 +558,15 @@ func hookWrightScope(h harness.Harness, stdin io.Reader, stdout, stderr io.Write
562
558
  return exitOK
563
559
  }
564
560
  in := h.ParseGuardInput(bytes.NewReader(data))
565
- kind := devritesAgent(in.AgentType)
561
+ kind := devritesAgentForGuard(h, in)
562
+ genericCompat := os.Getenv("DEVRITES_CODEX_GENERIC_AGENT_COMPAT") == "1"
563
+ if in.AgentType == "" && in.AgentID == "" {
564
+ if code, handled := guardRootBuildWindow(h, data, in, stdout, stderr); handled {
565
+ return code
566
+ }
567
+ }
566
568
  if kind == devritesAgentNone &&
567
- os.Getenv("DEVRITES_CODEX_GENERIC_AGENT_COMPAT") == "1" &&
569
+ genericCompat &&
568
570
  in.AgentType == "" {
569
571
  return exitOK
570
572
  }
@@ -617,9 +619,6 @@ func hookWrightScope(h harness.Harness, stdin io.Reader, stdout, stderr io.Write
617
619
  if !wsIsFile(filepath.Join(dir, ".reconcile-base")) {
618
620
  return exitOK
619
621
  }
620
- if wsIsFile(filepath.Join(dir, ".reconcile-inline")) {
621
- return denyWright(h, stdout, stderr, wrightForbiddenReason, drvreason.HookWrightForbiddenDenied)
622
- }
623
622
  active = true
624
623
  }
625
624
 
@@ -1009,6 +1008,75 @@ func denyOrObserveWright(h harness.Harness, stdout, stderr io.Writer, dir string
1009
1008
  return exitOK
1010
1009
  }
1011
1010
 
1011
+ func guardRootBuildWindow(h harness.Harness, data []byte, in harness.GuardInput, stdout, stderr io.Writer) (int, bool) {
1012
+ root, _, dir, ok := resolveWorkspace()
1013
+ if !ok || !wsIsFile(filepath.Join(dir, ".reconcile-base")) {
1014
+ return exitOK, false
1015
+ }
1016
+
1017
+ if isAgentDispatchTool(in.ToolName) {
1018
+ return exitOK, true
1019
+ }
1020
+
1021
+ projectDir := filepath.Dir(root)
1022
+ if isOpaqueExecutionTool(in.ToolName) {
1023
+ return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
1024
+ }
1025
+ targets, suspicious := rootMutationTargets(data, in)
1026
+ for _, target := range targets {
1027
+ abs := target
1028
+ if !filepath.IsAbs(abs) {
1029
+ abs = filepath.Join(projectDir, abs)
1030
+ }
1031
+ abs = filepath.Clean(abs)
1032
+ resolved, err := resolveRootMutationTarget(abs)
1033
+ if err != nil {
1034
+ return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
1035
+ }
1036
+ if !underDevrites(resolved, root) {
1037
+ return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
1038
+ }
1039
+ }
1040
+ if suspicious {
1041
+ return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
1042
+ }
1043
+ if len(targets) > 0 {
1044
+ return exitOK, true
1045
+ }
1046
+ return exitOK, false
1047
+ }
1048
+
1049
+ func resolveRootMutationTarget(filename string) (string, error) {
1050
+ info, err := os.Lstat(filename)
1051
+ if err == nil && info.Mode()&os.ModeSymlink != 0 {
1052
+ target, err := os.Readlink(filename)
1053
+ if err != nil {
1054
+ return "", err
1055
+ }
1056
+ if !filepath.IsAbs(target) {
1057
+ target = filepath.Join(filepath.Dir(filename), target)
1058
+ }
1059
+ return safepath.ResolveExisting(target)
1060
+ }
1061
+ return safepath.ResolveExisting(filename)
1062
+ }
1063
+
1064
+ func rootMutationTargets(data []byte, in harness.GuardInput) ([]string, bool) {
1065
+ switch {
1066
+ case isEditTool(in.ToolName):
1067
+ targets := mutationToolPaths(data, in)
1068
+ return targets, len(targets) == 0
1069
+ case isShellTool(in.ToolName):
1070
+ targets, opaque := wrightShellWritePaths(in.Command)
1071
+ if len(targets) > 0 {
1072
+ return targets, false
1073
+ }
1074
+ return nil, opaque && (reviewerMutateRe.MatchString(in.Command) || opaqueInterpreterRe.MatchString(in.Command))
1075
+ default:
1076
+ return nil, false
1077
+ }
1078
+ }
1079
+
1012
1080
  func denyWright(h harness.Harness, stdout, stderr io.Writer, message string, reasonID drvreason.ID) int {
1013
1081
  out, err := h.PreToolDeny(message)
1014
1082
  if err != nil {
@@ -256,8 +256,6 @@ type strategyFlags struct {
256
256
  values map[CandidateID]string
257
257
  }
258
258
 
259
- func (s *strategyFlags) String() string { return "" }
260
-
261
259
  func (s *strategyFlags) Set(value string) error {
262
260
  idText, text, ok := strings.Cut(value, "=")
263
261
  id := CandidateID(idText)
@@ -66,7 +66,7 @@ var complianceMatrix = []Surface{
66
66
  {"Stop gate", Conditional, Conditional, "all rest points", "observe-only finding + manual host-specific rite-status", "yes", "high", "Both deliver Stop; stop-gate blocks with DEVRITES_STOP_GATE=enforce or DEVRITES_HOOK_PROFILE=strict."},
67
67
  {"SubagentStart discipline injection", Native, Native, "review/build fan-out", "agent file preamble", "yes", "high", "subagent-orient wired on both."},
68
68
  {"Skill invocation", Native, Adapter, "all public rites", "explicit file read", "yes", "high", "Claude: native /rite. Codex: $rite over the mirrored .agents/skills tree."},
69
- {"Reviewer subagent dispatch", Native, Instruction, "review/seal confidence", "labelled inline pass", "partial", "medium", "Codex under-fires embedded spawns (openai/codex#23496); falls back to .codex/agents + a labelled inline pass."},
69
+ {"Reviewer subagent dispatch", Native, Adapter, "review/seal confidence", "HITL stop; no root inline review", "yes", "high", "Codex V2 requires the exact named role and verifies its durable parent/child rollout because V2 collaboration lifecycle calls bypass hooks; V1 keeps the guarded explorer fallback."},
70
70
  {"Standards step-0 load", Native, Instruction, "all phases", "explicit standards read", "partial", "medium", "Claude: skill Reads core.md. Codex: an AGENTS.md directive to read it."},
71
71
  {"Project activation", Native, Conditional, "Codex-only startup", "inspect hooks/config, decide trust, then rerun doctor", "yes", "medium", "Codex silently skips every .codex/ layer in an untrusted project; trust remains an operator decision after inspection."},
72
72
  }
@@ -199,6 +199,7 @@ type GuardInput struct {
199
199
  ToolName string
200
200
  Command string
201
201
  FilePath string
202
+ SessionID string
202
203
  AgentType string
203
204
  AgentID string
204
205
  ToolResponse string
@@ -209,6 +210,7 @@ type GuardInput struct {
209
210
  // way the shell hooks' `JSON.stringify` node parse does.
210
211
  func (h Harness) ParseGuardInput(r io.Reader) GuardInput {
211
212
  var raw struct {
213
+ SessionID string `json:"session_id"`
212
214
  ToolName string `json:"tool_name"`
213
215
  ToolInput struct {
214
216
  Command string `json:"command"`
@@ -237,6 +239,7 @@ func (h Harness) ParseGuardInput(r io.Reader) GuardInput {
237
239
  ToolName: raw.ToolName,
238
240
  Command: raw.ToolInput.Command,
239
241
  FilePath: firstNonEmpty(raw.ToolInput.FilePath, raw.ToolInput.Path),
242
+ SessionID: raw.SessionID,
240
243
  AgentType: raw.AgentType,
241
244
  AgentID: raw.AgentID,
242
245
  ToolResponse: resp,
@@ -22,7 +22,7 @@ import (
22
22
  //
23
23
  // base is the project directory that contains the .devrites tree.
24
24
  //
25
- // 0 doubt covered, or not assessable (no log / no wright / inline build) → pass
25
+ // 0 doubt covered, or not assessable (no log / no wright) → pass
26
26
  // 1 wright dispatch(es) but ZERO doubt → verify against decisions.md
27
27
  // 2 usage (no slug)
28
28
  // 3 decisions.md records a stood decision with `doubt: MISSING`
@@ -39,23 +39,13 @@ func DoubtCoverage(root string, args []string, stdout, stderr io.Writer) int {
39
39
  dir := featureDir(root, slug)
40
40
 
41
41
  // A recorded stood decision that was never doubted is the unambiguous
42
- // failure: checked first, and on every path (it survives the inline-build
43
- // case below, where the footprint heuristic cannot apply).
42
+ // failure: checked first, and on every path.
44
43
  if dec, ok := readFileOK(filepath.Join(dir, "decisions.md")); ok && strings.Contains(dec, "doubt: MISSING") {
45
44
  fmt.Fprintln(stdout, "doubt-coverage: SKIPPED: a '## Decisions stood' entry in decisions.md has doubt: MISSING.")
46
45
  fmt.Fprintln(stdout, "A decision was stood and recorded but never doubted. Re-dispatch doubt or escalate.")
47
46
  return 3
48
47
  }
49
48
 
50
- // Inline build (Task tool unavailable): the wright + doubt ran in-context,
51
- // dispatching nothing, so the footprint heuristic does not apply: the
52
- // decisions.md check above is the assessment.
53
- if isFile(filepath.Join(dir, ".reconcile-inline")) {
54
- fmt.Fprintln(stdout, "doubt-coverage: inline build (.reconcile-inline): footprint heuristic n/a; no MISSING")
55
- fmt.Fprintln(stdout, "verdict recorded. Verify by hand that each stood decision carries a devrites-doubt verdict.")
56
- return 0
57
- }
58
-
59
49
  f, err := os.Open(filepath.Join(dir, "footprint.log"))
60
50
  if err != nil {
61
51
  fmt.Fprintln(stdout, "doubt-coverage: no footprint log: nothing to assess (pass)")
@@ -22,10 +22,7 @@ const (
22
22
  reconcileAllowlistName = ".reconcile-allowlist"
23
23
  reconcileCheckedName = ".reconcile-checked"
24
24
  reconcileDevritesName = ".reconcile-devrites"
25
- reconcileInlineName = ".reconcile-inline"
26
25
  reconcileObjectsName = ".reconcile-objects"
27
- reconcileObjectMarkerName = ".devrites-baseline"
28
- reconcileLegacyClaimedName = ".reconcile-claimed"
29
26
  defaultWrightAllowlistName = ".wright-allowlist"
30
27
  wrightAllowlistFileEnv = "DEVRITES_WRIGHT_ALLOWLIST_FILE"
31
28
  reconcileDevritesPathPrefix = ".devrites/"
@@ -63,14 +60,12 @@ func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
63
60
  base := filepath.Join(d, reconcileBaseName)
64
61
  capturedAllowlist := filepath.Join(d, reconcileAllowlistName)
65
62
  devritesSnapshot := filepath.Join(d, reconcileDevritesName)
66
- inline := filepath.Join(d, reconcileInlineName)
67
63
  objects := filepath.Join(d, reconcileObjectsName)
68
- legacyClaimed := filepath.Join(d, reconcileLegacyClaimedName)
69
64
  checked := filepath.Join(d, reconcileCheckedName)
70
65
 
71
66
  closeWindow := func() error {
72
67
  var failures []string
73
- for _, privateFile := range []string{base, capturedAllowlist, devritesSnapshot, inline, legacyClaimed, checked} {
68
+ for _, privateFile := range []string{base, capturedAllowlist, devritesSnapshot, checked} {
74
69
  if err := os.Remove(privateFile); err != nil && !os.IsNotExist(err) {
75
70
  failures = append(failures, fmt.Sprintf("%s: %v", filepath.Base(privateFile), err))
76
71
  }
@@ -136,7 +131,7 @@ func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
136
131
  fmt.Fprintf(stderr, "reconcile: cannot refresh wright allowlist: %v\n", err)
137
132
  return 6
138
133
  }
139
- state, err := captureReconcileDevritesState(root, devritesSnapshot, activeObjects, checked, inline)
134
+ state, err := captureReconcileDevritesState(root, devritesSnapshot, activeObjects, checked)
140
135
  if err != nil {
141
136
  fmt.Fprintf(stderr, "reconcile: cannot refresh .devrites snapshot: %v\n", err)
142
137
  return 6
@@ -180,17 +175,12 @@ func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
180
175
  fmt.Fprintf(stderr, "reconcile: cannot write snapshot: %v\n", err)
181
176
  return 6
182
177
  }
183
- if err := os.WriteFile(filepath.Join(objects, reconcileObjectMarkerName), []byte(tree+"\n"), 0o600); err != nil {
184
- _ = closeWindow()
185
- fmt.Fprintf(stderr, "reconcile: cannot seal snapshot object database: %v\n", err)
186
- return 6
187
- }
188
178
  if err := os.WriteFile(capturedAllowlist, renderWrightAllowlist(allowed), 0o600); err != nil {
189
179
  _ = closeWindow()
190
180
  fmt.Fprintf(stderr, "reconcile: cannot capture wright allowlist: %v\n", err)
191
181
  return 6
192
182
  }
193
- state, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked, inline)
183
+ state, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked)
194
184
  if err != nil {
195
185
  _ = closeWindow()
196
186
  fmt.Fprintf(stderr, "reconcile: cannot snapshot .devrites: %v\n", err)
@@ -225,7 +215,7 @@ func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
225
215
  fmt.Fprintf(stderr, "reconcile: invalid .devrites snapshot: %v\n", err)
226
216
  return 6
227
217
  }
228
- afterDevrites, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked, inline)
218
+ afterDevrites, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked)
229
219
  if err != nil {
230
220
  fmt.Fprintf(stderr, "reconcile: cannot compare .devrites: %v\n", err)
231
221
  return 6
@@ -743,21 +733,6 @@ func loadReconcileBaseline(gitRoot, workspace string) (baseTree string, env []st
743
733
  if baseTree == "" || strings.ContainsAny(baseTree, " \t\r\n") {
744
734
  return "", nil, "", false, fmt.Errorf("%s does not contain one tree id", reconcileBaseName)
745
735
  }
746
- markerPath := filepath.Join(objectDir, reconcileObjectMarkerName)
747
- markerInfo, err := os.Lstat(markerPath)
748
- if err != nil {
749
- return "", nil, "", false, fmt.Errorf("%s is not a sealed object database: %w", reconcileObjectsName, err)
750
- }
751
- if !markerInfo.Mode().IsRegular() {
752
- return "", nil, "", false, fmt.Errorf("%s marker is not a regular file", reconcileObjectsName)
753
- }
754
- marker, err := os.ReadFile(markerPath)
755
- if err != nil {
756
- return "", nil, "", false, fmt.Errorf("read %s marker: %w", reconcileObjectsName, err)
757
- }
758
- if strings.TrimSpace(string(marker)) != baseTree {
759
- return "", nil, "", false, fmt.Errorf("%s marker does not match %s", reconcileObjectsName, reconcileBaseName)
760
- }
761
736
  env, err = reconcileGitEnv(gitRoot, objectDir)
762
737
  if err != nil {
763
738
  return "", nil, "", false, err
@@ -207,9 +207,8 @@ func TestReconcileCheckFlagsUnauthorizedChange(t *testing.T) {
207
207
  if code, out := runReconcile(t, root, "snapshot", "feat"); code != 0 {
208
208
  t.Fatalf("snapshot = %d, want 0\n%s", code, out)
209
209
  }
210
- // A writer cannot self-authorize a path by reporting it after dispatch.
210
+ // A writer cannot change a path that was not authorized at dispatch.
211
211
  writeFile(t, filepath.Join(gitRoot, "rogue.go"), "package main\n")
212
- writeFile(t, filepath.Join(root, "work", "feat", reconcileLegacyClaimedName), "rogue.go\n")
213
212
 
214
213
  code, out := runReconcile(t, root, "check", "feat")
215
214
  if code != 5 {
@@ -383,14 +382,20 @@ func TestReconcileCheckFailsClosedWhenObjectDatabaseIsMissing(t *testing.T) {
383
382
  }
384
383
 
385
384
  func TestReconcileCheckFailsClosedWhenObjectDatabaseIsCorrupt(t *testing.T) {
386
- newGitRepo(t)
385
+ gitRoot := newGitRepo(t)
387
386
  root := workspace(t, "feat")
388
387
  writeWrightAllowlist(t, root, "feat", "seed.go")
388
+ writeFile(t, filepath.Join(gitRoot, "seed.go"), "package seed\n")
389
389
  if code, out := runReconcile(t, root, "snapshot", "feat"); code != 0 {
390
390
  t.Fatalf("snapshot = %d, want 0\n%s", code, out)
391
391
  }
392
- marker := filepath.Join(featureDir(root, "feat"), reconcileObjectsName, reconcileObjectMarkerName)
393
- if err := os.Remove(marker); err != nil {
392
+ baseData, err := os.ReadFile(filepath.Join(featureDir(root, "feat"), reconcileBaseName))
393
+ if err != nil {
394
+ t.Fatal(err)
395
+ }
396
+ baseTree := strings.TrimSpace(string(baseData))
397
+ treeObject := filepath.Join(featureDir(root, "feat"), reconcileObjectsName, baseTree[:2], baseTree[2:])
398
+ if err := os.Remove(treeObject); err != nil {
394
399
  t.Fatal(err)
395
400
  }
396
401
 
@@ -398,7 +403,7 @@ func TestReconcileCheckFailsClosedWhenObjectDatabaseIsCorrupt(t *testing.T) {
398
403
  if code != 6 {
399
404
  t.Fatalf("check = %d, want 6\n%s", code, out)
400
405
  }
401
- if !strings.Contains(out, "not a sealed object database") {
406
+ if !strings.Contains(out, "tree is unavailable") {
402
407
  t.Fatalf("missing corrupt-object diagnostic:\n%s", out)
403
408
  }
404
409
  }
@@ -502,23 +507,22 @@ func TestReconcileRefreshRequiresPriorCleanCheck(t *testing.T) {
502
507
  }
503
508
  }
504
509
 
505
- func TestReconcileCloseSupportsInlineFallback(t *testing.T) {
510
+ func TestReconcileCloseClearsPrivateWindowState(t *testing.T) {
506
511
  newGitRepo(t)
507
512
  root := workspace(t, "feat")
508
513
  writeFile(t, filepath.Join(root, "work", "feat", reconcileBaseName), "stale\n")
509
- writeFile(t, filepath.Join(root, "work", "feat", reconcileInlineName), "")
510
514
  if err := os.MkdirAll(filepath.Join(root, "work", "feat", reconcileObjectsName), 0o755); err != nil {
511
515
  t.Fatal(err)
512
516
  }
513
517
 
514
518
  code, out := runReconcile(t, root, "close", "feat")
515
519
  if code != 0 {
516
- t.Fatalf("inline fallback close = %d, want 0\n%s", code, out)
520
+ t.Fatalf("close = %d, want 0\n%s", code, out)
517
521
  }
518
522
  if !strings.Contains(out, "closed slice window") {
519
523
  t.Errorf("expected close message:\n%s", out)
520
524
  }
521
525
  if isFile(filepath.Join(featureDir(root, "feat"), reconcileBaseName)) || isDir(filepath.Join(featureDir(root, "feat"), reconcileObjectsName)) {
522
- t.Fatal("inline fallback close retained private baseline state")
526
+ t.Fatal("close retained private baseline state")
523
527
  }
524
528
  }
package/engine/main.go CHANGED
@@ -93,7 +93,8 @@ Hooks:
93
93
  hook orient Emit SessionStart orientation for the active feature
94
94
  hook stop-gate Refuse to end a turn at a provably inconsistent rest point
95
95
  hook reviewer-readonly Deny every mutation/dispatch surface for read-only DevRites agents
96
- hook subagent-orient Inject the DevRites discipline into a spawned devrites-* subagent
96
+ hook agent-dispatch Bind Codex spawn, start, wait, result, and reconcile receipts
97
+ hook subagent-orient Inject DevRites discipline and bind a spawned specialist role
97
98
  hook cursor Re-inject the active feature's cursor (UserPromptSubmit)
98
99
  hook statusline One-line workspace HUD (settings.json statusLine)
99
100
  hook redwatch Fail-on-red sentinel: set/clear .red after a test/build command
@@ -510,6 +511,8 @@ func cmdHook(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
510
511
  return hookStopGate(h, stdin, stdout, stderr)
511
512
  case "reviewer-readonly":
512
513
  return hookReviewerReadonly(h, stdin, stdout, stderr)
514
+ case "agent-dispatch":
515
+ return hookAgentDispatch(h, stdin, stdout, stderr)
513
516
  case "subagent-orient":
514
517
  return hookSubagentOrient(h, stdin, stdout, stderr)
515
518
  case "cursor":