devrites 6.1.1 → 6.1.3

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 (221) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/docs/cli.md +4 -1
  4. package/docs/command-map.md +1 -1
  5. package/docs/engine/commands.md +4 -4
  6. package/docs/orchestration.md +4 -3
  7. package/docs/usage.md +1 -1
  8. package/engine/commands.go +1 -1
  9. package/engine/help.go +5 -1
  10. package/engine/internal/acceptance/cli.go +14 -5
  11. package/engine/internal/acceptance/run.go +20 -5
  12. package/engine/internal/lib/apply_bundle.go +474 -0
  13. package/engine/internal/lib/claims.go +12 -2
  14. package/engine/internal/lib/claims_holder.go +124 -0
  15. package/engine/internal/lib/cli_observe.go +14 -0
  16. package/engine/internal/lib/diffscope.go +2 -1
  17. package/engine/internal/lib/taskgraph.go +124 -0
  18. package/engine/internal/parallel/cli.go +111 -9
  19. package/engine/internal/parallel/lease.go +58 -4
  20. package/engine/internal/parallel/ops.go +244 -75
  21. package/engine/internal/parallel/select.go +31 -6
  22. package/engine/main.go +4 -0
  23. package/pack/.claude/agents/devrites-plan-drafter.md +13 -3
  24. package/pack/.claude/agents/devrites-slice-wright.md +5 -1
  25. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
  26. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
  27. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +6 -2
  28. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +3 -0
  29. package/pack/.claude/skills/devrites-lib/reference/standards/gates.md +3 -0
  30. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +7 -0
  31. package/pack/.claude/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
  32. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
  33. package/pack/.claude/skills/rite-autocomplete/SKILL.md +2 -2
  34. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +1 -1
  35. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
  36. package/pack/.claude/skills/rite-build/SKILL.md +7 -7
  37. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +2 -2
  38. package/pack/.claude/skills/rite-build/reference/checkpoint.md +1 -1
  39. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
  40. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +28 -21
  41. package/pack/.claude/skills/rite-build/reference/phase-contract.md +10 -1
  42. package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +3 -2
  43. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +9 -2
  44. package/pack/.claude/skills/rite-define/SKILL.md +11 -2
  45. package/pack/.claude/skills/rite-handoff/SKILL.md +1 -1
  46. package/pack/.claude/skills/rite-plan/SKILL.md +20 -9
  47. package/pack/.claude/skills/rite-plan/reference/slicing.md +5 -0
  48. package/pack/.claude/skills/rite-polish/SKILL.md +6 -4
  49. package/pack/.claude/skills/rite-prove/SKILL.md +3 -2
  50. package/pack/.claude/skills/rite-review/SKILL.md +6 -1
  51. package/pack/.claude/skills/rite-seal/SKILL.md +2 -2
  52. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +7 -1
  53. package/pack/.claude/skills/rite-vet/SKILL.md +9 -5
  54. package/pack/.claude/skills/rite-vet/reference/artifacts.md +8 -0
  55. package/pack/.claude/skills/rite-vet/reference/review-axes.md +1 -1
  56. package/pack/generated/claude/agents/devrites-plan-drafter.md +13 -3
  57. package/pack/generated/claude/agents/devrites-slice-wright.md +5 -1
  58. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
  59. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
  60. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +6 -2
  61. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +3 -0
  62. package/pack/generated/claude/skills/devrites-lib/reference/standards/gates.md +3 -0
  63. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +7 -0
  64. package/pack/generated/claude/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
  65. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
  66. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +2 -2
  67. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +1 -1
  68. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
  69. package/pack/generated/claude/skills/rite-build/SKILL.md +7 -7
  70. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +2 -2
  71. package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +1 -1
  72. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
  73. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +28 -21
  74. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +10 -1
  75. package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +3 -2
  76. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +9 -2
  77. package/pack/generated/claude/skills/rite-define/SKILL.md +11 -2
  78. package/pack/generated/claude/skills/rite-handoff/SKILL.md +1 -1
  79. package/pack/generated/claude/skills/rite-plan/SKILL.md +20 -9
  80. package/pack/generated/claude/skills/rite-plan/reference/slicing.md +5 -0
  81. package/pack/generated/claude/skills/rite-polish/SKILL.md +6 -4
  82. package/pack/generated/claude/skills/rite-prove/SKILL.md +3 -2
  83. package/pack/generated/claude/skills/rite-review/SKILL.md +6 -1
  84. package/pack/generated/claude/skills/rite-seal/SKILL.md +2 -2
  85. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +7 -1
  86. package/pack/generated/claude/skills/rite-vet/SKILL.md +9 -5
  87. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +8 -0
  88. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +1 -1
  89. package/pack/generated/codex/agents/devrites-plan-drafter.toml +13 -3
  90. package/pack/generated/codex/agents/devrites-slice-wright.toml +5 -1
  91. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
  92. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
  93. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +6 -2
  94. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +3 -0
  95. package/pack/generated/codex/skills/devrites-lib/reference/standards/gates.md +3 -0
  96. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +7 -0
  97. package/pack/generated/codex/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
  98. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
  99. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +2 -2
  100. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +1 -1
  101. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
  102. package/pack/generated/codex/skills/rite-build/SKILL.md +7 -7
  103. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +2 -2
  104. package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +1 -1
  105. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +3 -2
  106. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +28 -21
  107. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +10 -1
  108. package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +3 -2
  109. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +9 -2
  110. package/pack/generated/codex/skills/rite-define/SKILL.md +11 -2
  111. package/pack/generated/codex/skills/rite-handoff/SKILL.md +1 -1
  112. package/pack/generated/codex/skills/rite-plan/SKILL.md +20 -9
  113. package/pack/generated/codex/skills/rite-plan/reference/slicing.md +5 -0
  114. package/pack/generated/codex/skills/rite-polish/SKILL.md +6 -4
  115. package/pack/generated/codex/skills/rite-prove/SKILL.md +3 -2
  116. package/pack/generated/codex/skills/rite-review/SKILL.md +6 -1
  117. package/pack/generated/codex/skills/rite-seal/SKILL.md +2 -2
  118. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +7 -1
  119. package/pack/generated/codex/skills/rite-vet/SKILL.md +9 -5
  120. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +8 -0
  121. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +1 -1
  122. package/pack/generated/devin/agents/devrites-plan-drafter.md +13 -3
  123. package/pack/generated/devin/agents/devrites-slice-wright.md +5 -1
  124. package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
  125. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
  126. package/pack/generated/devin/skills/devrites-lib/reference/standards/agents.md +6 -2
  127. package/pack/generated/devin/skills/devrites-lib/reference/standards/core.md +3 -0
  128. package/pack/generated/devin/skills/devrites-lib/reference/standards/gates.md +3 -0
  129. package/pack/generated/devin/skills/devrites-lib/reference/standards/testing.md +7 -0
  130. package/pack/generated/devin/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
  131. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
  132. package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +2 -2
  133. package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +1 -1
  134. package/pack/generated/devin/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
  135. package/pack/generated/devin/skills/rite-build/SKILL.md +7 -7
  136. package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +2 -2
  137. package/pack/generated/devin/skills/rite-build/reference/checkpoint.md +1 -1
  138. package/pack/generated/devin/skills/rite-build/reference/one-slice-cycle.md +3 -2
  139. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +28 -21
  140. package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +10 -1
  141. package/pack/generated/devin/skills/rite-build/reference/spec-drift-guard.md +3 -2
  142. package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +9 -2
  143. package/pack/generated/devin/skills/rite-define/SKILL.md +11 -2
  144. package/pack/generated/devin/skills/rite-handoff/SKILL.md +1 -1
  145. package/pack/generated/devin/skills/rite-plan/SKILL.md +20 -9
  146. package/pack/generated/devin/skills/rite-plan/reference/slicing.md +5 -0
  147. package/pack/generated/devin/skills/rite-polish/SKILL.md +6 -4
  148. package/pack/generated/devin/skills/rite-prove/SKILL.md +3 -2
  149. package/pack/generated/devin/skills/rite-review/SKILL.md +6 -1
  150. package/pack/generated/devin/skills/rite-seal/SKILL.md +2 -2
  151. package/pack/generated/devin/skills/rite-seal/reference/phase-contract.md +7 -1
  152. package/pack/generated/devin/skills/rite-vet/SKILL.md +9 -5
  153. package/pack/generated/devin/skills/rite-vet/reference/artifacts.md +8 -0
  154. package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +1 -1
  155. package/pack/generated/omp/agents/devrites-plan-drafter.md +13 -3
  156. package/pack/generated/omp/agents/devrites-slice-wright.md +5 -1
  157. package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
  158. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
  159. package/pack/generated/omp/skills/devrites-lib/reference/standards/agents.md +6 -2
  160. package/pack/generated/omp/skills/devrites-lib/reference/standards/core.md +3 -0
  161. package/pack/generated/omp/skills/devrites-lib/reference/standards/gates.md +3 -0
  162. package/pack/generated/omp/skills/devrites-lib/reference/standards/testing.md +7 -0
  163. package/pack/generated/omp/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
  164. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
  165. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +2 -2
  166. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +1 -1
  167. package/pack/generated/omp/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
  168. package/pack/generated/omp/skills/rite-build/SKILL.md +7 -7
  169. package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +2 -2
  170. package/pack/generated/omp/skills/rite-build/reference/checkpoint.md +1 -1
  171. package/pack/generated/omp/skills/rite-build/reference/one-slice-cycle.md +3 -2
  172. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +28 -21
  173. package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +10 -1
  174. package/pack/generated/omp/skills/rite-build/reference/spec-drift-guard.md +3 -2
  175. package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +9 -2
  176. package/pack/generated/omp/skills/rite-define/SKILL.md +11 -2
  177. package/pack/generated/omp/skills/rite-handoff/SKILL.md +1 -1
  178. package/pack/generated/omp/skills/rite-plan/SKILL.md +20 -9
  179. package/pack/generated/omp/skills/rite-plan/reference/slicing.md +5 -0
  180. package/pack/generated/omp/skills/rite-polish/SKILL.md +6 -4
  181. package/pack/generated/omp/skills/rite-prove/SKILL.md +3 -2
  182. package/pack/generated/omp/skills/rite-review/SKILL.md +6 -1
  183. package/pack/generated/omp/skills/rite-seal/SKILL.md +2 -2
  184. package/pack/generated/omp/skills/rite-seal/reference/phase-contract.md +7 -1
  185. package/pack/generated/omp/skills/rite-vet/SKILL.md +9 -5
  186. package/pack/generated/omp/skills/rite-vet/reference/artifacts.md +8 -0
  187. package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +1 -1
  188. package/pack/generated/pi/agents/devrites-plan-drafter.md +13 -3
  189. package/pack/generated/pi/agents/devrites-slice-wright.md +5 -1
  190. package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +14 -5
  191. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +24 -9
  192. package/pack/generated/pi/skills/devrites-lib/reference/standards/agents.md +6 -2
  193. package/pack/generated/pi/skills/devrites-lib/reference/standards/core.md +3 -0
  194. package/pack/generated/pi/skills/devrites-lib/reference/standards/gates.md +3 -0
  195. package/pack/generated/pi/skills/devrites-lib/reference/standards/testing.md +7 -0
  196. package/pack/generated/pi/skills/devrites-lib/reference/standards/workflow-artifacts.md +10 -6
  197. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +1 -1
  198. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +2 -2
  199. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +1 -1
  200. package/pack/generated/pi/skills/rite-autocomplete/reference/stop-conditions.md +6 -2
  201. package/pack/generated/pi/skills/rite-build/SKILL.md +7 -7
  202. package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +2 -2
  203. package/pack/generated/pi/skills/rite-build/reference/checkpoint.md +1 -1
  204. package/pack/generated/pi/skills/rite-build/reference/one-slice-cycle.md +3 -2
  205. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +28 -21
  206. package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +10 -1
  207. package/pack/generated/pi/skills/rite-build/reference/spec-drift-guard.md +3 -2
  208. package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +9 -2
  209. package/pack/generated/pi/skills/rite-define/SKILL.md +11 -2
  210. package/pack/generated/pi/skills/rite-handoff/SKILL.md +1 -1
  211. package/pack/generated/pi/skills/rite-plan/SKILL.md +20 -9
  212. package/pack/generated/pi/skills/rite-plan/reference/slicing.md +5 -0
  213. package/pack/generated/pi/skills/rite-polish/SKILL.md +6 -4
  214. package/pack/generated/pi/skills/rite-prove/SKILL.md +3 -2
  215. package/pack/generated/pi/skills/rite-review/SKILL.md +6 -1
  216. package/pack/generated/pi/skills/rite-seal/SKILL.md +2 -2
  217. package/pack/generated/pi/skills/rite-seal/reference/phase-contract.md +7 -1
  218. package/pack/generated/pi/skills/rite-vet/SKILL.md +9 -5
  219. package/pack/generated/pi/skills/rite-vet/reference/artifacts.md +8 -0
  220. package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +1 -1
  221. package/package.json +1 -1
@@ -90,70 +90,182 @@ func createLocked(opts CreateOpts) (*Lease, error) {
90
90
  session = fmt.Sprintf("pid:%d", os.Getpid())
91
91
  }
92
92
 
93
- createdPaths := make([]string, 0, len(opts.Slices))
94
- createdBranches := make([]string, 0, len(opts.Slices))
95
- cleanupPartial := func() {
96
- for _, p := range createdPaths {
97
- if err := removeWorktree(opts.Root, p); err != nil {
98
- warnf("worktree cleanup %s: %v", p, err)
93
+ slices, cleanupPartial, err := createWorktrees(opts.Root, opts.Slug, opts.BatchID, base, opts.Slices)
94
+ if err != nil {
95
+ return nil, err
96
+ }
97
+
98
+ lease := &Lease{
99
+ BatchID: opts.BatchID,
100
+ CreatedAt: NowUTC(),
101
+ BaseSHA: base,
102
+ N: len(slices),
103
+ Status: StatusRunning,
104
+ ControlPIDOrSession: session,
105
+ Slices: slices,
106
+ }
107
+ if err := WriteLease(leasePath, lease); err != nil {
108
+ cleanupPartial()
109
+ return nil, err
110
+ }
111
+ ensureScratchExcluded(opts.Root)
112
+ return lease, nil
113
+ }
114
+
115
+ // createWorktrees makes one pending worker worktree and branch per slice from
116
+ // base, all or nothing: a failure removes what this call already made. The
117
+ // returned rollback undoes a success when the caller's lease write fails.
118
+ func createWorktrees(root, slug, batchID, base string, specs []SlicePaths) ([]LeaseSlice, func(), error) {
119
+ made := make([]LeaseSlice, 0, len(specs))
120
+ rollback := func() {
121
+ // Worktrees first: git refuses to delete a branch still checked out.
122
+ for _, sl := range made {
123
+ if err := removeWorktree(root, sl.WorktreePath); err != nil {
124
+ warnf("worktree cleanup %s: %v", sl.WorktreePath, err)
99
125
  }
100
126
  }
101
- for _, b := range createdBranches {
102
- if err := deleteBranch(opts.Root, b); err != nil {
103
- warnf("branch cleanup %s: %v", b, err)
127
+ for _, sl := range made {
128
+ if err := deleteBranch(root, sl.Branch); err != nil {
129
+ warnf("branch cleanup %s: %v", sl.Branch, err)
104
130
  }
105
131
  }
106
132
  }
107
- slices := make([]LeaseSlice, 0, len(opts.Slices))
133
+ for _, sp := range specs {
134
+ sl, err := createWorktree(root, slug, batchID, base, sp)
135
+ if err != nil {
136
+ rollback()
137
+ return nil, nil, err
138
+ }
139
+ made = append(made, sl)
140
+ }
141
+ return made, rollback, nil
142
+ }
143
+
144
+ func createWorktree(root, slug, batchID, base string, sp SlicePaths) (LeaseSlice, error) {
145
+ if err := validateSliceID(sp.ID); err != nil {
146
+ return LeaseSlice{}, err
147
+ }
148
+ paths, err := validateSlicePaths(sp.Paths, fmt.Sprintf("slice %q", sp.ID), root)
149
+ if err != nil {
150
+ return LeaseSlice{}, err
151
+ }
152
+ branch := WorkerBranch(slug, batchID, sp.ID)
153
+ wt := WorkerWorktreePath(root, batchID, sp.ID)
154
+ if _, err := os.Stat(wt); err == nil {
155
+ return LeaseSlice{}, fmt.Errorf("worktree path already exists: %s", wt)
156
+ }
157
+ if err := os.MkdirAll(filepath.Dir(wt), 0o755); err != nil {
158
+ return LeaseSlice{}, err
159
+ }
160
+ if err := worktreeAdd(root, wt, branch, base); err != nil {
161
+ return LeaseSlice{}, fmt.Errorf("worktree add %s: %w", sp.ID, err)
162
+ }
163
+ return LeaseSlice{
164
+ ID: sp.ID,
165
+ Paths: paths,
166
+ WorktreePath: wt,
167
+ Branch: branch,
168
+ WrightStatus: WrightPending,
169
+ }, nil
170
+ }
171
+
172
+ // AddOpts configures refilling a running lease with new slices.
173
+ type AddOpts struct {
174
+ Root string
175
+ Slug string
176
+ Slices []SlicePaths
177
+ }
178
+
179
+ // Add refills freed slots of a running lease: each new slice gets a worktree
180
+ // from the current control tip, under the same path-disjoint and cap rules as
181
+ // the slices still active. Landed (integrated) slices no longer claim paths.
182
+ func Add(opts AddOpts) (lease *Lease, err error) {
183
+ err = withLeaseLock(opts.Root, opts.Slug, func() error {
184
+ lease, err = addLocked(opts)
185
+ return err
186
+ })
187
+ return lease, err
188
+ }
189
+
190
+ func addLocked(opts AddOpts) (*Lease, error) {
191
+ if abs, err := filepath.Abs(opts.Root); err == nil {
192
+ opts.Root = abs
193
+ }
194
+ if len(opts.Slices) == 0 {
195
+ return nil, fmt.Errorf("add requires at least one slice")
196
+ }
197
+ leasePath, err := LeasePath(opts.Root, opts.Slug)
198
+ if err != nil {
199
+ return nil, err
200
+ }
201
+ lease, err := ReadLease(leasePath)
202
+ if err != nil {
203
+ return nil, err
204
+ }
205
+ if lease.Status != StatusRunning {
206
+ return nil, fmt.Errorf("add requires status=running (have %s)", lease.Status)
207
+ }
208
+ head, err := revParse(opts.Root, expectedControl(lease))
209
+ if err != nil {
210
+ return nil, err
211
+ }
212
+ if err := ensureControlAtBase(opts.Root, head); err != nil {
213
+ return nil, err
214
+ }
215
+
216
+ seen := make(map[string]bool, len(lease.Slices)+len(opts.Slices))
217
+ for _, sl := range lease.Slices {
218
+ seen[sl.ID] = true
219
+ }
220
+ added := make([]SlicePaths, 0, len(opts.Slices))
221
+ var addedPaths []string
108
222
  for _, sp := range opts.Slices {
109
223
  if err := validateSliceID(sp.ID); err != nil {
110
- cleanupPartial()
111
224
  return nil, err
112
225
  }
226
+ if seen[sp.ID] {
227
+ return nil, fmt.Errorf("slice id %q already in lease", sp.ID)
228
+ }
229
+ seen[sp.ID] = true
113
230
  paths, err := validateSlicePaths(sp.Paths, fmt.Sprintf("slice %q", sp.ID), opts.Root)
114
231
  if err != nil {
115
- cleanupPartial()
116
232
  return nil, err
117
233
  }
118
- branch := WorkerBranch(opts.Slug, opts.BatchID, sp.ID)
119
- wt := WorkerWorktreePath(opts.Root, opts.BatchID, sp.ID)
120
- if _, err := os.Stat(wt); err == nil {
121
- cleanupPartial()
122
- return nil, fmt.Errorf("worktree path already exists: %s", wt)
123
- }
124
- if err := os.MkdirAll(filepath.Dir(wt), 0o755); err != nil {
125
- cleanupPartial()
234
+ added = append(added, SlicePaths{ID: sp.ID, Paths: paths})
235
+ addedPaths = append(addedPaths, paths...)
236
+ }
237
+ all := append(activeSlices(lease), added...)
238
+ if len(all) > MaxParallelSlices {
239
+ return nil, fmt.Errorf("add would make %d slices active; parallel allows at most %d", len(all), MaxParallelSlices)
240
+ }
241
+ if len(all) >= 2 {
242
+ if _, err := CheckPathDisjoint(all, opts.Root); err != nil {
126
243
  return nil, err
127
244
  }
128
- if err := worktreeAdd(opts.Root, wt, branch, base); err != nil {
129
- cleanupPartial()
130
- return nil, fmt.Errorf("worktree add %s: %w", sp.ID, err)
131
- }
132
- createdPaths = append(createdPaths, wt)
133
- createdBranches = append(createdBranches, branch)
134
- slices = append(slices, LeaseSlice{
135
- ID: sp.ID,
136
- Paths: paths,
137
- WorktreePath: wt,
138
- Branch: branch,
139
- WrightStatus: WrightPending,
140
- })
245
+ }
246
+ // The new worktree starts from control HEAD, so uncommitted user work on
247
+ // its paths would be neither in the slice nor safe from its landing.
248
+ dirty, err := porcelainDirtyPaths(opts.Root, addedPaths)
249
+ if err != nil {
250
+ return nil, err
251
+ }
252
+ if dirty {
253
+ return nil, fmt.Errorf("control has uncommitted changes on new slice paths; stop for the human to resolve them (never stash or commit them), then rerun add")
141
254
  }
142
255
 
143
- lease := &Lease{
144
- BatchID: opts.BatchID,
145
- CreatedAt: NowUTC(),
146
- BaseSHA: base,
147
- N: len(slices),
148
- Status: StatusRunning,
149
- ControlPIDOrSession: session,
150
- Slices: slices,
256
+ made, rollback, err := createWorktrees(opts.Root, opts.Slug, lease.BatchID, head, added)
257
+ if err != nil {
258
+ return nil, err
259
+ }
260
+ for i := range made {
261
+ made[i].BaseSHA = head
151
262
  }
263
+ lease.Slices = append(lease.Slices, made...)
264
+ lease.N = len(lease.Slices)
152
265
  if err := WriteLease(leasePath, lease); err != nil {
153
- cleanupPartial()
266
+ rollback()
154
267
  return nil, err
155
268
  }
156
- ensureScratchExcluded(opts.Root)
157
269
  return lease, nil
158
270
  }
159
271
 
@@ -185,6 +297,10 @@ func recordGreenLocked(repoRoot, slug, sliceID, commit string) (*Lease, error) {
185
297
  found := false
186
298
  for i := range lease.Slices {
187
299
  if lease.Slices[i].ID == sliceID {
300
+ if lease.Slices[i].WrightStatus == WrightIntegrated {
301
+ // Re-greening a landed slice would land its work a second time.
302
+ return nil, fmt.Errorf("slice %s already integrated", sliceID)
303
+ }
188
304
  lease.Slices[i].WrightStatus = WrightGreen
189
305
  lease.Slices[i].TransferCommit = sha
190
306
  found = true
@@ -222,11 +338,11 @@ func abortLocked(repoRoot, slug string) (*Lease, error) {
222
338
  if lease.Status == StatusComplete {
223
339
  return nil, fmt.Errorf("abort refused: lease already complete — the batch landed in control; nothing to discard")
224
340
  }
225
- base, err := revParse(repoRoot, lease.BaseSHA)
341
+ expected, err := revParse(repoRoot, expectedControl(lease))
226
342
  if err != nil {
227
343
  return nil, err
228
344
  }
229
- if err := ensureControlAtBase(repoRoot, base); err != nil {
345
+ if err := ensureControlAtBase(repoRoot, expected); err != nil {
230
346
  warnf("control not at base: %v", err)
231
347
  }
232
348
  lease.Status = StatusAborted
@@ -252,12 +368,17 @@ func ensureControlAtBase(repoRoot, base string) error {
252
368
 
253
369
  // IntegrateOpts configures staging integrate.
254
370
  type IntegrateOpts struct {
255
- Root string
256
- Slug string
371
+ Root string
372
+ Slug string
373
+ // Slice, when set, lands only that slice (requires ApplyToControl);
374
+ // otherwise every slice not yet integrated lands together.
375
+ Slice string
257
376
  ApplyToControl bool
258
377
  }
259
378
 
260
- // Integrate all-or-nothing applies sibling transfer commits onto a staging branch.
379
+ // Integrate all-or-nothing applies transfer commits onto a staging branch
380
+ // cut from the expected control tip — every not-yet-integrated slice, or just
381
+ // opts.Slice — and with ApplyToControl fast-forwards control to it.
261
382
  func Integrate(opts IntegrateOpts) (tip string, lease *Lease, err error) {
262
383
  err = withLeaseLock(opts.Root, opts.Slug, func() error {
263
384
  tip, lease, err = integrateLocked(opts)
@@ -267,6 +388,9 @@ func Integrate(opts IntegrateOpts) (tip string, lease *Lease, err error) {
267
388
  }
268
389
 
269
390
  func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
391
+ if opts.Slice != "" && !opts.ApplyToControl {
392
+ return "", nil, fmt.Errorf("integrate --slice requires --apply-to-control")
393
+ }
270
394
  leasePath, err := LeasePath(opts.Root, opts.Slug)
271
395
  if err != nil {
272
396
  return "", nil, err
@@ -278,17 +402,36 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
278
402
  if lease.Status != StatusRunning && lease.Status != StatusIntegrateFailed {
279
403
  return "", nil, fmt.Errorf("integrate requires status=running|integrate-failed (have %s)", lease.Status)
280
404
  }
281
- base, err := revParse(opts.Root, lease.BaseSHA)
405
+ // Control may only have moved by this lease's own landings; anything else
406
+ // is external work that integrate must neither rewind nor build on.
407
+ expected, err := revParse(opts.Root, expectedControl(lease))
282
408
  if err != nil {
283
409
  return "", nil, err
284
410
  }
285
- if err := ensureControlAtBase(opts.Root, base); err != nil {
411
+ if err := ensureControlAtBase(opts.Root, expected); err != nil {
286
412
  return "", nil, err
287
413
  }
288
414
 
289
- union := make([]string, 0, len(lease.Slices))
290
- for _, sl := range lease.Slices {
291
- union = append(union, sl.Paths...)
415
+ var targets []int
416
+ for i, sl := range lease.Slices {
417
+ switch {
418
+ case opts.Slice == "" && sl.WrightStatus != WrightIntegrated,
419
+ opts.Slice != "" && sl.ID == opts.Slice:
420
+ targets = append(targets, i)
421
+ }
422
+ }
423
+ switch {
424
+ case opts.Slice != "" && len(targets) == 0:
425
+ return "", nil, fmt.Errorf("slice not in lease: %s", opts.Slice)
426
+ case opts.Slice != "" && lease.Slices[targets[0]].WrightStatus == WrightIntegrated:
427
+ return "", nil, fmt.Errorf("slice %s already integrated", opts.Slice)
428
+ case len(targets) == 0:
429
+ return "", nil, fmt.Errorf("no slices left to integrate")
430
+ }
431
+
432
+ union := make([]string, 0, len(targets))
433
+ for _, i := range targets {
434
+ union = append(union, lease.Slices[i].Paths...)
292
435
  }
293
436
  if opts.ApplyToControl {
294
437
  // FF into control must not silently absorb or clobber the user's
@@ -303,7 +446,8 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
303
446
  }
304
447
  }
305
448
 
306
- for _, sl := range lease.Slices {
449
+ for _, i := range targets {
450
+ sl := lease.Slices[i]
307
451
  if sl.WrightStatus != WrightGreen {
308
452
  return "", nil, fmt.Errorf("slice %s is not green", sl.ID)
309
453
  }
@@ -326,7 +470,7 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
326
470
  if err := os.MkdirAll(filepath.Dir(stageWT), 0o755); err != nil {
327
471
  return "", nil, err
328
472
  }
329
- if err := worktreeAdd(opts.Root, stageWT, ibranch, base); err != nil {
473
+ if err := worktreeAdd(opts.Root, stageWT, ibranch, expected); err != nil {
330
474
  return "", nil, fmt.Errorf("staging worktree: %w", err)
331
475
  }
332
476
 
@@ -350,13 +494,20 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
350
494
  leasePath, StatusRunning, retryErr)
351
495
  }
352
496
  }
353
- if err := ensureControlAtBase(opts.Root, base); err != nil {
497
+ if err := ensureControlAtBase(opts.Root, expected); err != nil {
354
498
  warnf("control diverged from base during integrate: %v", err)
355
499
  }
356
500
  return "", lease, reason
357
501
  }
358
502
 
359
- for _, sl := range lease.Slices {
503
+ for _, i := range targets {
504
+ sl := lease.Slices[i]
505
+ // Each slice is proven against the commit its worktree was cut from,
506
+ // which for a refill is a later control tip than the batch base.
507
+ base, err := revParse(opts.Root, sliceBase(lease, sl))
508
+ if err != nil {
509
+ return fail(fmt.Errorf("slice %s base: %w", sl.ID, err))
510
+ }
360
511
  tc, err := revParse(opts.Root, sl.TransferCommit)
361
512
  if err != nil {
362
513
  return fail(fmt.Errorf("slice %s transfer: %w", sl.ID, err))
@@ -417,11 +568,20 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
417
568
  if err := mergeFFOnly(opts.Root, tip); err != nil {
418
569
  return fail(fmt.Errorf("control fast-forward to integrate tip failed: %w", err))
419
570
  }
420
- lease.Status = StatusComplete
571
+ for _, i := range targets {
572
+ lease.Slices[i].WrightStatus = WrightIntegrated
573
+ }
574
+ lease.ControlTip = tip
575
+ // A per-slice landing also clears an earlier integrate-failed: the
576
+ // lease is healthy again while other slices are still in flight.
577
+ lease.Status = StatusRunning
578
+ if len(activeSlices(lease)) == 0 {
579
+ lease.Status = StatusComplete
580
+ }
421
581
  if err := WriteLease(leasePath, lease); err != nil {
422
582
  return "", nil, err
423
583
  }
424
- } else if err := ensureControlAtBase(opts.Root, base); err != nil {
584
+ } else if err := ensureControlAtBase(opts.Root, expected); err != nil {
425
585
  return fail(err)
426
586
  }
427
587
  // Without --apply-to-control the lease stays running: the integrate
@@ -529,11 +689,12 @@ func salvageSlice(sl LeaseSlice, batchID string) (string, error) {
529
689
  fmt.Sprintf("devrites: salvage WIP (%s)", batchID), sl.Paths)
530
690
  }
531
691
 
532
- // Cleanup removes worker worktrees and clears the lease. A complete batch
533
- // deletes the merged slice branches; a forced non-complete cleanup first
534
- // salvages each sibling — uncommitted allowlisted changes become a commit on
535
- // the slice branch — and keeps every branch whose tip moved past base as the
536
- // durable rejected-work ref.
692
+ // Cleanup removes worker worktrees and clears the lease. A complete batch, and
693
+ // any slice already integrated, deletes the merged slice branches; a forced
694
+ // non-complete cleanup first salvages each other sibling — uncommitted
695
+ // allowlisted changes become a commit on the slice branch — and keeps every
696
+ // branch whose tip moved past that slice's base as the durable rejected-work
697
+ // ref.
537
698
  func Cleanup(repoRoot, slug string, force bool) (salvaged []Salvage, err error) {
538
699
  err = withLeaseLock(repoRoot, slug, func() error {
539
700
  salvaged, err = cleanupLocked(repoRoot, slug, force)
@@ -558,14 +719,6 @@ func cleanupLocked(repoRoot, slug string, force bool) ([]Salvage, error) {
558
719
  return nil, fmt.Errorf("cleanup refuses status=%s without --force (non-complete cleanup salvages work onto slice branches)", lease.Status)
559
720
  }
560
721
  complete := lease.Status == StatusComplete
561
- base := ""
562
- if !complete {
563
- if b, err := revParse(repoRoot, lease.BaseSHA); err != nil {
564
- warnf("base sha %s: %v", lease.BaseSHA, err)
565
- } else {
566
- base = b
567
- }
568
- }
569
722
  // Ownership comes from what git registered, read before anything is
570
723
  // removed, never from the lease's own claim.
571
724
  registered, err := registeredWorktrees(repoRoot)
@@ -576,6 +729,9 @@ func cleanupLocked(repoRoot, slug string, force bool) ([]Salvage, error) {
576
729
  var failed, stranded, unconfirmed []string
577
730
  keptWorktrees := map[string]bool{}
578
731
  for _, sl := range lease.Slices {
732
+ // A slice integrate already landed is done like a complete batch: its
733
+ // work is on control, so there is nothing to salvage or keep.
734
+ landed := complete || sl.WrightStatus == WrightIntegrated
579
735
  salvageFailed := false
580
736
  // Only the deterministic scratch layout is ours to touch. A forged or
581
737
  // stale lease worktree_path is never salvaged into (git ops would run
@@ -595,7 +751,7 @@ func cleanupLocked(repoRoot, slug string, force bool) ([]Salvage, error) {
595
751
  sl.ID, sl.WorktreePath, ScratchRoot(repoRoot))
596
752
  }
597
753
  }
598
- if !complete && owned {
754
+ if !landed && owned {
599
755
  if _, err := salvage(sl, lease.BatchID); err != nil {
600
756
  warnf("salvage %s: %v", sl.ID, err)
601
757
  salvageFailed = true
@@ -631,7 +787,13 @@ func cleanupLocked(repoRoot, slug string, force bool) ([]Salvage, error) {
631
787
  if sl.Branch != "" {
632
788
  keep := salvageFailed
633
789
  tip := ""
634
- if !complete {
790
+ if !landed {
791
+ base := ""
792
+ if b, err := revParse(repoRoot, sliceBase(lease, sl)); err != nil {
793
+ warnf("base sha %s: %v", sliceBase(lease, sl), err)
794
+ } else {
795
+ base = b
796
+ }
635
797
  if t, err := revParse(repoRoot, sl.Branch); err != nil {
636
798
  warnf("branch tip %s: %v", sl.Branch, err)
637
799
  keep = true // fail toward preservation
@@ -740,13 +902,20 @@ func StatusReport(repoRoot, slug string) (string, error) {
740
902
  fmt.Fprintf(&b, "lease: %s\n", leasePath)
741
903
  fmt.Fprintf(&b, "status: %s\n", lease.Status)
742
904
  fmt.Fprintf(&b, "base_sha: %s\n", lease.BaseSHA)
905
+ if lease.ControlTip != "" {
906
+ fmt.Fprintf(&b, "control_tip: %s\n", lease.ControlTip)
907
+ }
743
908
  for _, sl := range lease.Slices {
744
909
  tc := sl.TransferCommit
745
910
  if tc == "" {
746
911
  tc = "-"
747
912
  }
748
- fmt.Fprintf(&b, "slice %s: wright=%s wt=%s branch=%s transfer=%s\n",
913
+ fmt.Fprintf(&b, "slice %s: wright=%s wt=%s branch=%s transfer=%s",
749
914
  sl.ID, sl.WrightStatus, sl.WorktreePath, sl.Branch, tc)
915
+ if sl.BaseSHA != "" {
916
+ fmt.Fprintf(&b, " base=%s", sl.BaseSHA)
917
+ }
918
+ b.WriteString("\n")
750
919
  }
751
920
  if head, err := headSHA(repoRoot); err == nil {
752
921
  fmt.Fprintf(&b, "control_HEAD: %s\n", head)
@@ -13,6 +13,13 @@ var checkPathDisjoint = CheckPathDisjoint
13
13
  // whose size is ≤ cap. ready must already be dependency-satisfied and in plan
14
14
  // order. cap is 1–MaxParallelSlices; 1 is a legal one-slice round, not a latch.
15
15
  func SelectGreedy(cap int, ready []SlicePaths, root string) ([]SlicePaths, error) {
16
+ return SelectGreedySeeded(cap, nil, ready, root)
17
+ }
18
+
19
+ // SelectGreedySeeded is SelectGreedy with seed slices already running: they
20
+ // occupy cap slots and claim their paths, a ready candidate with a seed's id
21
+ // is skipped, and only the new picks are returned.
22
+ func SelectGreedySeeded(cap int, seed, ready []SlicePaths, root string) ([]SlicePaths, error) {
16
23
  if cap < 1 || cap > MaxParallelSlices {
17
24
  return nil, fmt.Errorf("cap must be 1-%d (got %d)", MaxParallelSlices, cap)
18
25
  }
@@ -39,11 +46,18 @@ func SelectGreedy(cap int, ready []SlicePaths, root string) ([]SlicePaths, error
39
46
  normalized = append(normalized, SlicePaths{ID: item.ID, Paths: paths})
40
47
  }
41
48
 
42
- selected := make([]SlicePaths, 0, min(cap, len(normalized)))
49
+ running := make(map[string]bool, len(seed))
50
+ for _, s := range seed {
51
+ running[s.ID] = true
52
+ }
53
+ selected := append([]SlicePaths{}, seed...)
43
54
  for _, cand := range normalized {
44
55
  if len(selected) >= cap {
45
56
  break
46
57
  }
58
+ if running[cand.ID] {
59
+ continue
60
+ }
47
61
  if len(selected) == 0 {
48
62
  selected = append(selected, cand)
49
63
  continue
@@ -57,7 +71,7 @@ func SelectGreedy(cap int, ready []SlicePaths, root string) ([]SlicePaths, error
57
71
  }
58
72
  selected = trial
59
73
  }
60
- return selected, nil
74
+ return selected[len(seed):], nil
61
75
  }
62
76
 
63
77
  func selectMode(n int) string {
@@ -71,10 +85,21 @@ func selectMode(n int) string {
71
85
  }
72
86
  }
73
87
 
74
- func formatSelect(selected []SlicePaths) string {
75
- ids := make([]string, len(selected))
76
- for i, item := range selected {
88
+ // formatSelect prints the select line. running < 0 means no lease seeded the
89
+ // selection; otherwise new picks join that many running slices, so the mode
90
+ // counts them too and the line reports running=<k>.
91
+ func formatSelect(picks []SlicePaths, running int) string {
92
+ ids := make([]string, len(picks))
93
+ for i, item := range picks {
77
94
  ids[i] = item.ID
78
95
  }
79
- return fmt.Sprintf("select: n_eff=%d mode=%s slices=%s", len(selected), selectMode(len(selected)), strings.Join(ids, ","))
96
+ mode := selectMode(len(picks))
97
+ if running > 0 && len(picks) > 0 {
98
+ mode = selectMode(len(picks) + running)
99
+ }
100
+ line := fmt.Sprintf("select: n_eff=%d mode=%s slices=%s", len(picks), mode, strings.Join(ids, ","))
101
+ if running >= 0 {
102
+ line += fmt.Sprintf(" running=%d", running)
103
+ }
104
+ return line
80
105
  }
package/engine/main.go CHANGED
@@ -65,6 +65,8 @@ Usage:
65
65
  devrites-engine gates <subcommand> <slug> Machine-checked gates.md ledger: scaffold/status/run/reverify/lint/attest/abandon
66
66
  devrites-engine state resolve <qid> "<ans>" Resolve an open question and update state atomically
67
67
  devrites-engine state merge-manifest <slug> [pred...] Fold the recorded predecessor chain's manifests into the release candidate manifest
68
+ devrites-engine state apply-bundle <slug> <bundle.json>... [--dry-run]
69
+ Validate and apply plan-drafter hunk bundles atomically
68
70
  devrites-engine state close <slug> Archive a shipped feature and clear ACTIVE
69
71
  devrites-engine migrate <slug> [--dry-run] [--answer id=choice] Normalize an older-schema workspace to the current schema
70
72
  devrites-engine secret-scan [--staged] [--stdin] [slug] Scan exact staged blobs, stdin, or touched files; HIGH blocks
@@ -237,6 +239,8 @@ func cmdState(root string, args []string, stdout, stderr io.Writer) int {
237
239
  return lib.Resolve(root, args[1:], stdout, stderr)
238
240
  case "merge-manifest":
239
241
  return lib.RunMergeManifest(root, args[1:], stdout, stderr)
242
+ case "apply-bundle":
243
+ return lib.RunApplyBundle(root, args[1:], stdout, stderr)
240
244
  case "close":
241
245
  return lib.CloseOut(root, args[1:], stdout, stderr)
242
246
  default:
@@ -21,7 +21,8 @@ seams supplied by the orchestrator.
21
21
  planning artifacts while preserving settled intent.
22
22
 
23
23
  Reuse existing seams and dependencies before proposing new ones. Build vertical
24
- slices in dependency order. Map every REQ and AC to a slice and executable proof,
24
+ slices in dependency order; put a hub file's shared edit in one seam slice
25
+ ([slicing.md](../skills/rite-plan/reference/slicing.md#slice-independence)). Map every REQ and AC to a slice and executable proof,
25
26
  name rollback and failure paths; keep acceptance-changing proposals separate. Durable
26
27
  proof commands must be portable repository commands, never host-local wrappers,
27
28
  user-specific absolute paths, or temporary proof trees.
@@ -75,10 +76,13 @@ Return this result:
75
76
 
76
77
  ```yaml
77
78
  mode: define | repair
79
+ base_hashes: {} # <path>: sha256 of the bytes you read, for every replace/patch target
78
80
  candidate_files:
79
81
  - path: <requested canonical target>
80
82
  operation: create | replace | patch
81
- content: <complete candidate or exact bounded delta>
83
+ content: <complete file; create/replace only>
84
+ hunks: [] # patch only: {id, old_text, new_text}; old_text byte-exact, unique in the pinned file
85
+ retired_ids: [] # tracked IDs (AC/REQ/HZN/SLICE/DEC/...) the delta removes on purpose
82
86
  acceptance_map: []
83
87
  dependency_order: []
84
88
  technical_decisions: []
@@ -88,7 +92,13 @@ applicability_disposition: []
88
92
  self_attack_exposures: []
89
93
  ```
90
94
 
91
- The root validates the cross-file bundle and persists accepted content.
95
+ The root saves the bundle as JSON and applies it with `devrites-engine state
96
+ apply-bundle <slug> <bundle.json>`, which refuses a stale pin, a missing,
97
+ duplicate, or overlapping `old_text`, an unlisted vanished ID, a new task-graph
98
+ problem, new budget overshoot, or a new dated delta section, and writes nothing on refusal. Pin each
99
+ `base_hashes` entry with `shasum -a 256` (or `sha256sum`) on the exact file you
100
+ read, and prefer short anchored hunks to whole-file `replace`. Patch the owning
101
+ clause in place; never add a dated delta section beside superseded text.
92
102
 
93
103
  ## Tools / read-write mode
94
104
 
@@ -103,7 +103,11 @@ unclear boundary is underspecified: return `Escalation` without proceeding.
103
103
  that source in the result. Never invent an API.
104
104
  - **No process residue.** Do not put `SLICE-###`, slice numbers, or "this slice"
105
105
  in product source, tests, comments, or filenames.
106
- 4. **VERIFY (fail-on-red).** Run writer-safe tests/types/lint. Report required
106
+ 4. **VERIFY (fail-on-red).** Run writer-safe tests/types/lint: `Tests/proof` plus
107
+ tests of changed code; `## Feature gates` rows are root-owned. Writer-safe means
108
+ local and non-consumptive — syntax/type/lint, unit and self-check suites, and a
109
+ narrow smoke of the changed path — never only a syntax check; contract text
110
+ cannot strip it. Report required
107
111
  build/browser/E2E as `not-run` (root-owned, artifact-producing gates), and only
108
112
  when the exact command, cwd, and prerequisites already appear in the unchanged
109
113
  vetted `test-plan.md`; never synthesize or rewrite a root command. Root runs
@@ -4,7 +4,9 @@ Host dispatch; DevRites governs.
4
4
 
5
5
  ## Roster
6
6
 
7
- Review: Spec/Code plus triggered frontend/security/performance; Seal: all applicable roles.
7
+ Review: Spec/Code, triggered frontend/security/performance, and the seal roles that can
8
+ judge the frozen digest (test-analyst, doubt, devex); Seal: all applicable roles, citing
9
+ same-candidate Review accounts.
8
10
 
9
11
  | Reviewer | Fires | Trigger |
10
12
  |---|---|---|
@@ -74,8 +76,10 @@ If the candidate diff has no matching surfaces, record
74
76
  and dedupe by evidence/root cause. Account for all seven (and, in `seal.md`, `devrites-proof-runner` and `devrites-doubt-reviewer`) in
75
77
  `review.md`/`seal.md`, each [engineering lane](#engineering-lanes) separately:
76
78
  `Outcome:` plus admitted account, or
77
- `Not-applicable: <inspected-scope reason>`. Spec/Code may cite valid unchanged
78
- `review.md` under [evidence validity](candidate-integrity.md#evidence-validity); persist no dispatch telemetry.
79
+ `Not-applicable: <inspected-scope reason>`. Seal cites, rather than re-dispatches, any
80
+ role `review.md` admitted on the identical candidate under
81
+ [evidence validity](candidate-integrity.md#evidence-validity); a changed digest or newly
82
+ triggered surface needs a fresh account. Persist no dispatch telemetry.
79
83
  7. Missing profile, null/failure, or malformed output is a `gap`; never
80
84
  infer approval or substitute inline review.
81
85
 
@@ -127,7 +131,8 @@ child's handle, path/resource ownership and capacity reservation until the
127
131
  native host confirms a terminal state and returned work has been reconciled.
128
132
  Query or wait on that handle; do not replace the child, release its slot, or
129
133
  reuse its resources while it may still run. If terminal state cannot be
130
- established, record a gap and retain ownership. A result classification alone
134
+ established, record a gap and retain ownership; for a child launched by an earlier
135
+ session, the claim proof below settles it. A result classification alone
131
136
  does not prove host termination.
132
137
 
133
138
  On resume (compaction or a new session), run `devrites-engine dispatch <slug>
@@ -135,7 +140,11 @@ status`, `parallel status`, and `claim list --all` before any dispatch. A read-o
135
140
  wave whose handles this session cannot query: `abandon` it, record each unreturned
136
141
  role `gap`, and re-dispatch fresh on the current digest; its late returns are
137
142
  `stale`. A writer keeps its claim and lease — no relaunch on those paths until the
138
- claim is released or its holder is proven terminal. A wright that never returned an
143
+ claim is released or its holder is proven terminal. For an unqueryable writer,
144
+ `claim list --all` showing its claim `released` or `dead` (the engine proved the
145
+ holding session exited on this host) is that proof: `abandon` the wave, record the
146
+ role `gap`, claim the paths, and re-dispatch without waiting on the launching
147
+ session. `live` or `expired` stays held. A wright that never returned an
139
148
  admitted result leaves unadmitted bytes: task-path changes since its pre-dispatch
140
149
  `git diff --name-only` baseline are never cited as built nor discarded, and are
141
150
  named in the re-dispatch contract; changes outside task paths stop for the human