devrites 6.0.2 → 6.0.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 (161) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -1
  3. package/docs/adr/0034-workflow-artifact-byte-corrections-get-code-review.md +60 -0
  4. package/docs/adr/README.md +2 -1
  5. package/docs/engine/commands.md +8 -8
  6. package/engine/help.go +12 -7
  7. package/engine/help_test.go +1 -0
  8. package/engine/internal/gate/readiness_binding.go +19 -0
  9. package/engine/internal/lib/cli_observe.go +16 -0
  10. package/engine/internal/lib/context.go +197 -19
  11. package/engine/internal/lib/context_next_test.go +238 -0
  12. package/engine/internal/lib/dispatch.go +189 -15
  13. package/engine/internal/lib/drift.go +10 -2
  14. package/engine/internal/lib/drift_test.go +29 -0
  15. package/engine/internal/lib/metrics.go +74 -1
  16. package/engine/internal/lib/observe_budgets_test.go +32 -0
  17. package/engine/internal/lib/observe_summary.go +8 -0
  18. package/engine/internal/lib/slicecheck.go +19 -0
  19. package/engine/internal/lib/slicecheck_test.go +41 -0
  20. package/engine/internal/lib/windows.go +109 -13
  21. package/engine/internal/lib/windows_test.go +85 -0
  22. package/engine/main.go +3 -3
  23. package/pack/.claude/agents/devrites-slice-wright.md +7 -2
  24. package/pack/.claude/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  25. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  26. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +1 -1
  27. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  28. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +3 -0
  29. package/pack/.claude/skills/devrites-lib/reference/standards/glossary.md +1 -1
  30. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +1 -0
  31. package/pack/.claude/skills/devrites-lib/reference/standards/windows.md +11 -3
  32. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  33. package/pack/.claude/skills/rite-build/SKILL.md +1 -1
  34. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +3 -1
  35. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
  36. package/pack/.claude/skills/rite-build/reference/output.md +1 -1
  37. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +5 -3
  38. package/pack/.claude/skills/rite-build/reference/phase-contract.md +7 -3
  39. package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +3 -1
  40. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +23 -9
  41. package/pack/.claude/skills/rite-define/SKILL.md +2 -1
  42. package/pack/.claude/skills/rite-plan/SKILL.md +2 -1
  43. package/pack/.claude/skills/rite-pressure-test/SKILL.md +6 -3
  44. package/pack/.claude/skills/rite-vet/SKILL.md +9 -1
  45. package/pack/.claude/skills/rite-vet/reference/review-axes.md +6 -2
  46. package/pack/generated/claude/agents/devrites-slice-wright.md +7 -2
  47. package/pack/generated/claude/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  48. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  49. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +1 -1
  50. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  51. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +3 -0
  52. package/pack/generated/claude/skills/devrites-lib/reference/standards/glossary.md +1 -1
  53. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +1 -0
  54. package/pack/generated/claude/skills/devrites-lib/reference/standards/windows.md +11 -3
  55. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  56. package/pack/generated/claude/skills/rite-build/SKILL.md +1 -1
  57. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +3 -1
  58. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
  59. package/pack/generated/claude/skills/rite-build/reference/output.md +1 -1
  60. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +5 -3
  61. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +7 -3
  62. package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +3 -1
  63. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +23 -9
  64. package/pack/generated/claude/skills/rite-define/SKILL.md +2 -1
  65. package/pack/generated/claude/skills/rite-plan/SKILL.md +2 -1
  66. package/pack/generated/claude/skills/rite-pressure-test/SKILL.md +6 -3
  67. package/pack/generated/claude/skills/rite-vet/SKILL.md +9 -1
  68. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +6 -2
  69. package/pack/generated/codex/agents/devrites-slice-wright.toml +7 -2
  70. package/pack/generated/codex/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  71. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  72. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +1 -1
  73. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  74. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +3 -0
  75. package/pack/generated/codex/skills/devrites-lib/reference/standards/glossary.md +1 -1
  76. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +1 -0
  77. package/pack/generated/codex/skills/devrites-lib/reference/standards/windows.md +11 -3
  78. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  79. package/pack/generated/codex/skills/rite-build/SKILL.md +1 -1
  80. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +3 -1
  81. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +3 -2
  82. package/pack/generated/codex/skills/rite-build/reference/output.md +1 -1
  83. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +5 -3
  84. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +7 -3
  85. package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +3 -1
  86. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +23 -9
  87. package/pack/generated/codex/skills/rite-define/SKILL.md +2 -1
  88. package/pack/generated/codex/skills/rite-plan/SKILL.md +2 -1
  89. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +6 -3
  90. package/pack/generated/codex/skills/rite-vet/SKILL.md +9 -1
  91. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +6 -2
  92. package/pack/generated/devin/agents/devrites-slice-wright.md +7 -2
  93. package/pack/generated/devin/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  94. package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  95. package/pack/generated/devin/skills/devrites-lib/reference/standards/README.md +1 -1
  96. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  97. package/pack/generated/devin/skills/devrites-lib/reference/standards/code-review.md +3 -0
  98. package/pack/generated/devin/skills/devrites-lib/reference/standards/glossary.md +1 -1
  99. package/pack/generated/devin/skills/devrites-lib/reference/standards/security.md +1 -0
  100. package/pack/generated/devin/skills/devrites-lib/reference/standards/windows.md +11 -3
  101. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  102. package/pack/generated/devin/skills/rite-build/SKILL.md +1 -1
  103. package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +3 -1
  104. package/pack/generated/devin/skills/rite-build/reference/one-slice-cycle.md +3 -2
  105. package/pack/generated/devin/skills/rite-build/reference/output.md +1 -1
  106. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +5 -3
  107. package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +7 -3
  108. package/pack/generated/devin/skills/rite-build/reference/spec-drift-guard.md +3 -1
  109. package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +23 -9
  110. package/pack/generated/devin/skills/rite-define/SKILL.md +2 -1
  111. package/pack/generated/devin/skills/rite-plan/SKILL.md +2 -1
  112. package/pack/generated/devin/skills/rite-pressure-test/SKILL.md +6 -3
  113. package/pack/generated/devin/skills/rite-vet/SKILL.md +9 -1
  114. package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +6 -2
  115. package/pack/generated/omp/agents/devrites-slice-wright.md +7 -2
  116. package/pack/generated/omp/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  117. package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  118. package/pack/generated/omp/skills/devrites-lib/reference/standards/README.md +1 -1
  119. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  120. package/pack/generated/omp/skills/devrites-lib/reference/standards/code-review.md +3 -0
  121. package/pack/generated/omp/skills/devrites-lib/reference/standards/glossary.md +1 -1
  122. package/pack/generated/omp/skills/devrites-lib/reference/standards/security.md +1 -0
  123. package/pack/generated/omp/skills/devrites-lib/reference/standards/windows.md +11 -3
  124. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  125. package/pack/generated/omp/skills/rite-build/SKILL.md +1 -1
  126. package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +3 -1
  127. package/pack/generated/omp/skills/rite-build/reference/one-slice-cycle.md +3 -2
  128. package/pack/generated/omp/skills/rite-build/reference/output.md +1 -1
  129. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +5 -3
  130. package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +7 -3
  131. package/pack/generated/omp/skills/rite-build/reference/spec-drift-guard.md +3 -1
  132. package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +23 -9
  133. package/pack/generated/omp/skills/rite-define/SKILL.md +2 -1
  134. package/pack/generated/omp/skills/rite-plan/SKILL.md +2 -1
  135. package/pack/generated/omp/skills/rite-pressure-test/SKILL.md +6 -3
  136. package/pack/generated/omp/skills/rite-vet/SKILL.md +9 -1
  137. package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +6 -2
  138. package/pack/generated/pi/agents/devrites-slice-wright.md +7 -2
  139. package/pack/generated/pi/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  140. package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  141. package/pack/generated/pi/skills/devrites-lib/reference/standards/README.md +1 -1
  142. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  143. package/pack/generated/pi/skills/devrites-lib/reference/standards/code-review.md +3 -0
  144. package/pack/generated/pi/skills/devrites-lib/reference/standards/glossary.md +1 -1
  145. package/pack/generated/pi/skills/devrites-lib/reference/standards/security.md +1 -0
  146. package/pack/generated/pi/skills/devrites-lib/reference/standards/windows.md +11 -3
  147. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  148. package/pack/generated/pi/skills/rite-build/SKILL.md +1 -1
  149. package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +3 -1
  150. package/pack/generated/pi/skills/rite-build/reference/one-slice-cycle.md +3 -2
  151. package/pack/generated/pi/skills/rite-build/reference/output.md +1 -1
  152. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +5 -3
  153. package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +7 -3
  154. package/pack/generated/pi/skills/rite-build/reference/spec-drift-guard.md +3 -1
  155. package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +23 -9
  156. package/pack/generated/pi/skills/rite-define/SKILL.md +2 -1
  157. package/pack/generated/pi/skills/rite-plan/SKILL.md +2 -1
  158. package/pack/generated/pi/skills/rite-pressure-test/SKILL.md +6 -3
  159. package/pack/generated/pi/skills/rite-vet/SKILL.md +9 -1
  160. package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +6 -2
  161. package/package.json +1 -1
@@ -7,6 +7,7 @@ import (
7
7
  "io"
8
8
  "os"
9
9
  "path/filepath"
10
+ "slices"
10
11
  "sort"
11
12
  "strconv"
12
13
  "strings"
@@ -23,7 +24,7 @@ const MetricsFile = "metrics.jsonl"
23
24
  const metricsUsage = `usage: devrites-engine metrics <record|summary> ...
24
25
 
25
26
  metrics record <slug> --phase <p> --event <e> [--role r] [--bytes n] [--note s]
26
- metrics summary [slug]
27
+ metrics summary [slug] [--by-slice]
27
28
  `
28
29
 
29
30
  type metricEvent struct {
@@ -150,6 +151,8 @@ func recordMetric(root, slug, phase, event, role string, bytes int64) {
150
151
  }
151
152
 
152
153
  func metricsSummary(root string, args []string, stdout, stderr io.Writer) int {
154
+ bySlice := slices.Contains(args, "--by-slice")
155
+ args = slices.DeleteFunc(slices.Clone(args), func(a string) bool { return a == "--by-slice" })
153
156
  slug, code, err := ActiveSlug(root, args)
154
157
  if err != nil {
155
158
  fmt.Fprintf(stderr, "metrics: %v\n", err)
@@ -163,6 +166,9 @@ func metricsSummary(root string, args []string, stdout, stderr io.Writer) int {
163
166
  fmt.Fprintf(stderr, "metrics: %v\n", err)
164
167
  return 2
165
168
  }
169
+ if bySlice {
170
+ return sliceCostSummary(root, slug, stdout, stderr)
171
+ }
166
172
  f, err := os.Open(filepath.Join(dir, MetricsFile)) // #nosec G304 -- dir is a validated workspace and filename is fixed
167
173
  if err != nil {
168
174
  fmt.Fprintf(stdout, "metrics: no events recorded for %s\n", slug)
@@ -258,3 +264,70 @@ func metricsSummary(root string, args []string, stdout, stderr io.Writer) int {
258
264
  fmt.Fprintln(stdout)
259
265
  return 0
260
266
  }
267
+
268
+ // sliceCostSummary reports, per slice named on dispatch waves, the build wright
269
+ // rounds, slice-bound Vet rechecks, and agent-minutes (role start to return; a
270
+ // wave naming several slices splits its minutes evenly). Abandoned waves are
271
+ // excluded, and waves recorded before returns were timestamped count rounds only.
272
+ func sliceCostSummary(root, slug string, stdout, stderr io.Writer) int {
273
+ f, _, err := loadDispatch(root, slug)
274
+ if err != nil {
275
+ fmt.Fprintf(stderr, "metrics: %v\n", err)
276
+ return 2
277
+ }
278
+ type cost struct {
279
+ wright, vet int
280
+ minutes float64
281
+ timed bool
282
+ }
283
+ per := map[string]*cost{}
284
+ for _, w := range f.Waves {
285
+ kind := roundKind(w)
286
+ if kind == "" || w.State == "abandoned" {
287
+ continue
288
+ }
289
+ minutes, timed := 0.0, false
290
+ for _, r := range w.Roles {
291
+ start, startErr := time.Parse(time.RFC3339, r.StartedAt)
292
+ end, endErr := time.Parse(time.RFC3339, r.ReturnedAt)
293
+ if startErr == nil && endErr == nil && !end.Before(start) {
294
+ minutes += end.Sub(start).Minutes()
295
+ timed = true
296
+ }
297
+ }
298
+ for _, id := range w.Slices {
299
+ c := per[id]
300
+ if c == nil {
301
+ c = &cost{}
302
+ per[id] = c
303
+ }
304
+ if kind == "wright" {
305
+ c.wright++
306
+ } else {
307
+ c.vet++
308
+ }
309
+ if timed {
310
+ c.minutes += minutes / float64(len(w.Slices))
311
+ c.timed = true
312
+ }
313
+ }
314
+ }
315
+ if len(per) == 0 {
316
+ fmt.Fprintf(stdout, "metrics: no slice-tagged dispatch waves for %s\n", slug)
317
+ return 0
318
+ }
319
+ ids := make([]string, 0, len(per))
320
+ for id := range per {
321
+ ids = append(ids, id)
322
+ }
323
+ sort.Strings(ids)
324
+ for _, id := range ids {
325
+ c := per[id]
326
+ minutes := "n/a"
327
+ if c.timed {
328
+ minutes = strconv.FormatFloat(c.minutes, 'f', 0, 64)
329
+ }
330
+ fmt.Fprintf(stdout, "metrics: %s wright_rounds=%d vet_rechecks=%d agent_minutes=%s\n", id, c.wright, c.vet, minutes)
331
+ }
332
+ return 0
333
+ }
@@ -394,3 +394,35 @@ func TestObserveArtifactBudgetsOversizedArtifactIsOverWithoutFullRead(t *testing
394
394
  t.Fatalf("allocated %d bytes for a %d byte artifact; the read must be bounded", allocated, size)
395
395
  }
396
396
  }
397
+
398
+ func TestUnreturnedWavesWarnAndSurfaceInOrient(t *testing.T) {
399
+ root, _ := writeBudgetWorkspace(t, map[string]string{
400
+ "state.md": "| phase | build |\n| schema | 4 |\n",
401
+ "tasks.md": budgetTasks,
402
+ })
403
+ open := func(wave string) string {
404
+ stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
405
+ args := []string{"feature", "open", "--phase", "build", "--wave", wave, "--role", "slice-wright", "--slice", "SLICE-001"}
406
+ if code := RunDispatch(root, args, stdout, stderr); code != 0 {
407
+ t.Fatalf("open %s: code=%d\n%s%s", wave, code, stdout, stderr)
408
+ }
409
+ return stdout.String()
410
+ }
411
+ if out := open("r1"); strings.Contains(out, "WARNING") {
412
+ t.Fatalf("first wave must not warn:\n%s", out)
413
+ }
414
+ if out := open("r2"); !strings.Contains(out, "WARNING: SLICE-001 earlier wave(s) r1 never returned") {
415
+ t.Fatalf("unreturned earlier wave not warned:\n%s", out)
416
+ }
417
+ summary, err := ObserveSummaryFor(root, "feature")
418
+ if err != nil {
419
+ t.Fatal(err)
420
+ }
421
+ if len(summary.InflightWaves) != 2 {
422
+ t.Fatalf("inflight waves=%+v", summary.InflightWaves)
423
+ }
424
+ first := summary.InflightWaves[0]
425
+ if first.Wave != "r1" || first.State != "open" || len(first.Pending) != 1 || first.Pending[0] != "slice-wright" || first.Slices[0] != "SLICE-001" {
426
+ t.Fatalf("inflight wave=%+v", first)
427
+ }
428
+ }
@@ -51,6 +51,7 @@ type ObserveSummary struct {
51
51
  ArtifactBudgets []ArtifactBudget `json:"artifact_budgets,omitempty"`
52
52
  BulkFiles []BulkFile `json:"bulk_files,omitempty"`
53
53
  Sequence *ObserveSequenceCursor `json:"sequence,omitempty"`
54
+ InflightWaves []InflightWave `json:"inflight_waves,omitempty"`
54
55
  }
55
56
 
56
57
  // ObserveSummaryFor builds a summary for one feature slug.
@@ -91,6 +92,13 @@ func ObserveSummaryFor(root, slug string) (ObserveSummary, error) {
91
92
  OK: len(graph.Problems) == 0,
92
93
  }
93
94
  }
95
+ // Open or sealed waves are agents whose results the root never recorded;
96
+ // a resume reconciles them before starting new work.
97
+ if waves, _, err := loadDispatch(root, slug); err == nil {
98
+ summary.InflightWaves = inflightWaves(waves)
99
+ } else {
100
+ summary.Unreadable = append(summary.Unreadable, "dispatch.json")
101
+ }
94
102
  if report.SequenceParent != "" || report.SequencePosition != "" || report.SequenceWorkspacesRemaining != "" || report.SequenceRole != "" {
95
103
  summary.Sequence = &ObserveSequenceCursor{
96
104
  Parent: report.SequenceParent,
@@ -29,6 +29,22 @@ var (
29
29
  windowsDrivePath = regexp.MustCompile(`^[A-Za-z]:[\\/]`)
30
30
  )
31
31
 
32
+ // sliceFileAdvisory is the Writer allowlist size at which a slice is flagged
33
+ // for an acceptance-preserving split. Advisory only: it never changes an exit
34
+ // code. Measured on real plans, slices at or above it averaged ~10 wright
35
+ // rounds versus ~3.5 below it.
36
+ const sliceFileAdvisory = 12
37
+
38
+ // sliceSizeAdvisory returns the Writer allowlist size and whether the slice is
39
+ // oversized without a recorded `Size rationale:`.
40
+ func sliceSizeAdvisory(block string) (int, bool) {
41
+ fields := sliceFields(block)
42
+ raw, _ := fieldValue(fields, "writer allowlist")
43
+ n := len(parsePathList(raw))
44
+ rationale, _ := fieldValue(fields, "size rationale")
45
+ return n, n >= sliceFileAdvisory && rationale == ""
46
+ }
47
+
32
48
  // sliceFields extracts the single-line `Field: value` pairs of a slice block.
33
49
  func sliceFields(block string) map[string]string {
34
50
  fields := map[string]string{}
@@ -169,6 +185,9 @@ func RunCheckSlice(root string, args []string, stdout, stderr io.Writer) int {
169
185
  problems = append(problems, "Tests/proof field missing or empty — no evidence plan for the slice")
170
186
  }
171
187
 
188
+ if n, over := sliceSizeAdvisory(block); over {
189
+ fmt.Fprintf(stdout, "check slice: ADVISORY size: %s has %d Writer allowlist files (>= %d); consider an acceptance-preserving split\n", sliceID, n, sliceFileAdvisory)
190
+ }
172
191
  if len(problems) > 0 {
173
192
  fmt.Fprintf(stdout, "check slice: %s BLOCKED\n", sliceID)
174
193
  for _, p := range problems {
@@ -127,3 +127,44 @@ func TestCheckSliceTouchedComparesNormalizedPaths(t *testing.T) {
127
127
  t.Fatalf("code=%d want 0\n%s%s", code, stdout, stderr)
128
128
  }
129
129
  }
130
+
131
+ func bigSliceTasks(rationale string) string {
132
+ var paths []string
133
+ for i := 1; i <= sliceFileAdvisory; i++ {
134
+ paths = append(paths, "`src/f"+strings.Repeat("x", i)+".go`")
135
+ }
136
+ return "# Tasks\n\n## SLICE-001 Big\nGoal: do it.\nSatisfies: AC-001\ndepends_on: []\nWriter allowlist: " +
137
+ strings.Join(paths, ", ") + "\nTests/proof: go test ./...\n" + rationale +
138
+ "\n## SLICE-002 Small\nGoal: tiny.\nSatisfies: AC-001\ndepends_on: [SLICE-001]\nWriter allowlist: `src/a.go`\nTests/proof: go test ./...\n"
139
+ }
140
+
141
+ func TestSliceSizeAdvisoryNeverBlocks(t *testing.T) {
142
+ root := sliceWorkspace(t, bigSliceTasks(""), "## Acceptance criteria\n- AC-001 works\n")
143
+ stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
144
+ if code := RunCheckSlice(root, []string{"feat", "SLICE-001"}, stdout, stderr); code != 0 {
145
+ t.Fatalf("advisory must not block: code=%d\n%s%s", code, stdout, stderr)
146
+ }
147
+ if !strings.Contains(stdout.String(), "ADVISORY size: SLICE-001 has 12 Writer allowlist files") {
148
+ t.Fatalf("advisory missing:\n%s", stdout)
149
+ }
150
+ stdout.Reset()
151
+ if code := RunTaskGraphCheck(root, "feat", stdout, stderr); code != 0 {
152
+ t.Fatalf("task-graph code=%d\n%s%s", code, stdout, stderr)
153
+ }
154
+ out := stdout.String()
155
+ if !strings.Contains(out, "task-graph: ADVISORY size: SLICE-001 has 12") || strings.Contains(out, "SLICE-002 has") {
156
+ t.Fatalf("task-graph advisory wrong:\n%s", out)
157
+ }
158
+ stdout.Reset()
159
+ if code := RunMetrics(root, []string{"summary", "feat"}, stdout, stderr); code != 0 || !strings.Contains(stdout.String(), "size-advisory=1") {
160
+ t.Fatalf("size-advisory metric not recorded: code=%d\n%s", code, stdout)
161
+ }
162
+ }
163
+
164
+ func TestSliceSizeRationaleSilencesAdvisory(t *testing.T) {
165
+ root := sliceWorkspace(t, bigSliceTasks("Size rationale: one generated golden set moves together\n"), "## Acceptance criteria\n- AC-001 works\n")
166
+ stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
167
+ if code := RunTaskGraphCheck(root, "feat", stdout, stderr); code != 0 || strings.Contains(stdout.String(), "ADVISORY") {
168
+ t.Fatalf("rationale must silence advisory: code=%d\n%s", code, stdout)
169
+ }
170
+ }
@@ -4,6 +4,7 @@ import (
4
4
  "fmt"
5
5
  "io"
6
6
  "os"
7
+ "path"
7
8
  "path/filepath"
8
9
  "regexp"
9
10
  "strconv"
@@ -12,17 +13,21 @@ import (
12
13
  "github.com/devrites/devrites/internal/devritespaths"
13
14
  )
14
15
 
15
- // windows.go — the broken-windows check. Review prose flags a new TODO, stub, or
16
- // skipped test as a finding; this gate makes the register countable instead: any
17
- // deferral marker the change *introduces* must carry a recorded waiver row in the
18
- // workspace's windows.md, or the check fails. A silent deferral is the bug.
16
+ // windows.go — the broken-windows check. Review prose flags a new TODO, stub,
17
+ // skipped or focused test, checker suppression, swallowed error, deleted test
18
+ // file, or stripped assertion as a finding; this gate makes the register
19
+ // countable instead: any such marker the change *introduces* must carry a
20
+ // recorded waiver row in the workspace's windows.md, or the check fails. A
21
+ // silent deferral or lowered bar is the bug.
19
22
  //
20
23
  // devrites-engine check windows <slug> [--worktree|--staged|--base <ref>] [--cwd <dir>]
21
24
  //
22
25
  // 0 no unwaived markers 2 usage error 3 unwaived markers or blocked
23
26
  //
24
27
  // Only added lines count: a marker already on the baseline is pre-existing debt,
25
- // never attributable to this change. Each windows.md row waives exactly one hit —
28
+ // never attributable to this change. Removals count once per file: a deleted
29
+ // test file (`deleted-test`) or a net loss of assertion lines in a test file
30
+ // that remains (`removed-assertion`). Each windows.md row waives exactly one hit —
26
31
  // one blanket row cannot cover a file's five new TODOs.
27
32
  //
28
33
  // windows.md row grammar (one waiver per line):
@@ -34,8 +39,10 @@ import (
34
39
 
35
40
  const windowsUsage = `usage: devrites-engine check windows <slug> [--worktree|--staged|--base <ref>] [--cwd <dir>]
36
41
 
37
- Reports deferral markers introduced by the change (added lines only) and requires
38
- each to be waived by a row in .devrites/work/<slug>/windows.md:
42
+ Reports deferral and bar-lowering markers introduced by the change (added lines:
43
+ TODO/stub/skip/focus idioms, checker suppressions, empty-catch; removals:
44
+ deleted-test, removed-assertion) and requires each to be waived by a row in
45
+ .devrites/work/<slug>/windows.md:
39
46
  - <path> | <marker> | <reason>
40
47
  Each row waives one hit at that path with that marker token.
41
48
  Exit 0 clean or fully waived, 3 unwaived markers or blocked.`
@@ -55,8 +62,22 @@ var deferralIdioms = []string{
55
62
  "panic(\"TODO", "panic(\"unimplemented", "panic(\"not implemented",
56
63
  "throw new Error(\"unimplemented", "throw new Error(\"not implemented",
57
64
  "TODO: implement", "not yet implemented",
65
+ // Focused tests and checker suppressions lower the bar the same way.
66
+ "it.only(", "describe.only(", "test.only(",
67
+ "@ts-ignore", "@ts-expect-error", "@ts-nocheck", "eslint-disable",
68
+ "biome-ignore", "deno-lint-ignore", "istanbul ignore", "c8 ignore",
69
+ "noqa", "type: ignore", "pylint: disable", "pragma: no cover",
70
+ "nolint", "#nosec", "nosemgrep", "NOSONAR", "gitleaks:allow",
71
+ "rubocop:disable", "@SuppressWarnings", "#[allow(", "shellcheck disable",
72
+ "Stryker disable",
58
73
  }
59
74
 
75
+ // emptyCatchRe catches a one-line swallowed error: `catch (e) {}`, `except: pass`.
76
+ var emptyCatchRe = regexp.MustCompile(`\bcatch\s*(?:\([^)]*\))?\s*\{\s*\}|\bexcept\b[^:\n]*:\s*pass\b`)
77
+
78
+ // assertionRe matches a line carrying a test assertion in common frameworks.
79
+ var assertionRe = regexp.MustCompile(`(?i)\bassert|XCTAssert|\bexpect\(|\bt\.(?:error|fatal)|\brequire\.|\.should\b`)
80
+
60
81
  type windowHit struct {
61
82
  Path string
62
83
  Line int
@@ -64,6 +85,14 @@ type windowHit struct {
64
85
  Text string
65
86
  }
66
87
 
88
+ // location is path:line, or the bare path for a file-level hit (Line 0).
89
+ func (h windowHit) location() string {
90
+ if h.Line == 0 {
91
+ return h.Path
92
+ }
93
+ return fmt.Sprintf("%s:%d", h.Path, h.Line)
94
+ }
95
+
67
96
  type windowWaiver struct {
68
97
  Path string
69
98
  Marker string
@@ -92,7 +121,73 @@ func windowsMarkers(line string) []string {
92
121
  out = append(out, m[1])
93
122
  }
94
123
  }
95
- return append(out, idioms...)
124
+ out = append(out, idioms...)
125
+ if emptyCatchRe.MatchString(line) {
126
+ out = append(out, "empty-catch")
127
+ }
128
+ return out
129
+ }
130
+
131
+ // isTestPath reports whether a project-relative path names a test source file.
132
+ func isTestPath(p string) bool {
133
+ base := path.Base(p)
134
+ for _, suffix := range []string{"_test.go", "_test.py", "_spec.rb", "Test.java", "Tests.java", "Test.kt", "Test.cs", "Tests.cs"} {
135
+ if strings.HasSuffix(base, suffix) {
136
+ return true
137
+ }
138
+ }
139
+ return strings.Contains(base, ".test.") || strings.Contains(base, ".spec.") ||
140
+ strings.HasPrefix(base, "test_") && strings.HasSuffix(base, ".py") ||
141
+ strings.Contains("/"+p, "/__tests__/") ||
142
+ strings.Contains("/"+p, "/tests/") && strings.HasSuffix(base, ".rs")
143
+ }
144
+
145
+ // removedTestHits reports bar-lowering removals in a `git diff -U0` stream: a
146
+ // deleted test file, or a test file that still exists but lost assertion lines
147
+ // on net (moving an assertion within the file is not a loss). Renames are not
148
+ // deletions when git rename detection pairs them.
149
+ func removedTestHits(diff string) []windowHit {
150
+ var hits []windowHit
151
+ net := map[string]int{}
152
+ var order []string
153
+ var oldPath, file string
154
+ inHeader, deleted := false, false
155
+ for _, line := range strings.Split(diff, "\n") {
156
+ switch {
157
+ case strings.HasPrefix(line, "diff --git "):
158
+ inHeader, deleted, file = true, false, ""
159
+ case inHeader && strings.HasPrefix(line, "--- "):
160
+ oldPath = strings.TrimPrefix(strings.Trim(strings.TrimSpace(line[4:]), `"`), "a/")
161
+ case inHeader && strings.HasPrefix(line, "+++ "):
162
+ newPath := strings.Trim(strings.TrimSpace(line[4:]), `"`)
163
+ if newPath == "/dev/null" {
164
+ deleted = true
165
+ if isTestPath(oldPath) {
166
+ hits = append(hits, windowHit{Path: oldPath, Marker: "deleted-test", Text: "test file deleted"})
167
+ }
168
+ continue
169
+ }
170
+ if file = strings.TrimPrefix(newPath, "b/"); !isTestPath(file) {
171
+ file = ""
172
+ }
173
+ case strings.HasPrefix(line, "@@"):
174
+ inHeader = false
175
+ case inHeader || deleted || file == "":
176
+ case strings.HasPrefix(line, "-") && assertionRe.MatchString(line[1:]):
177
+ if _, seen := net[file]; !seen {
178
+ order = append(order, file)
179
+ }
180
+ net[file]++
181
+ case strings.HasPrefix(line, "+") && assertionRe.MatchString(line[1:]):
182
+ net[file]--
183
+ }
184
+ }
185
+ for _, f := range order {
186
+ if n := net[f]; n > 0 {
187
+ hits = append(hits, windowHit{Path: f, Marker: "removed-assertion", Text: fmt.Sprintf("net -%d assertion line(s)", n)})
188
+ }
189
+ }
190
+ return hits
96
191
  }
97
192
 
98
193
  // addedLinesFromDiff parses a `git diff -U0` stream into added (path, line,
@@ -180,17 +275,18 @@ func windowsHits(opts diffScopeOpts) ([]windowHit, error) {
180
275
  var diffArgs []string
181
276
  switch opts.mode {
182
277
  case "staged":
183
- diffArgs = []string{"diff", "--cached", "-U0", "--"}
278
+ diffArgs = []string{"diff", "--cached", "-M", "-U0", "--"}
184
279
  case "base":
185
- diffArgs = []string{"diff", opts.base, "-U0", "--"}
280
+ diffArgs = []string{"diff", opts.base, "-M", "-U0", "--"}
186
281
  default:
187
- diffArgs = []string{"diff", "HEAD", "-U0", "--"}
282
+ diffArgs = []string{"diff", "HEAD", "-M", "-U0", "--"}
188
283
  }
189
284
  diff, err := runGitCommand(dir, nil, diffArgs...)
190
285
  var diffPaths map[string]bool
191
286
  switch {
192
287
  case err == nil:
193
288
  hits, diffPaths = addedLinesFromDiff(string(diff))
289
+ hits = append(hits, removedTestHits(string(diff))...)
194
290
  case opts.mode == "worktree" && isUnbornHEAD(dir):
195
291
  // No commits yet: every changed file is wholly new.
196
292
  diffPaths = map[string]bool{}
@@ -334,11 +430,11 @@ func RunCheckWindows(root string, args []string, stdout, stderr io.Writer) int {
334
430
  if waivers[w] > 0 {
335
431
  waivers[w]--
336
432
  waived++
337
- fmt.Fprintf(stdout, "windows: waived: %s:%d %s\n", h.Path, h.Line, h.Marker)
433
+ fmt.Fprintf(stdout, "windows: waived: %s %s\n", h.location(), h.Marker)
338
434
  continue
339
435
  }
340
436
  unwaived++
341
- fmt.Fprintf(stdout, "windows: unwaived: %s:%d %s — %s\n", h.Path, h.Line, h.Marker, h.Text)
437
+ fmt.Fprintf(stdout, "windows: unwaived: %s %s — %s\n", h.location(), h.Marker, h.Text)
342
438
  }
343
439
  switch {
344
440
  case len(hits) == 0:
@@ -57,6 +57,12 @@ func TestWindowsMarkersTokensAndIdioms(t *testing.T) {
57
57
  {"describe.skip(\"x\", fn)", []string{"describe.skip("}},
58
58
  {"raise NotImplementedError", []string{"NotImplementedError"}},
59
59
  {"panic(\"TODO wire cache\")", []string{"panic(\"TODO"}},
60
+ {"// @ts-ignore", []string{"@ts-ignore"}},
61
+ {"x = f() # noqa: E501", []string{"noqa"}},
62
+ {"it.only(\"focus\", fn)", []string{"it.only("}},
63
+ {"try { f() } catch (e) {}", []string{"empty-catch"}},
64
+ {"except ValueError: pass", []string{"empty-catch"}},
65
+ {"} catch (e) { log(e) }", nil},
60
66
  {"plain line", nil},
61
67
  }
62
68
  for _, tc := range cases {
@@ -333,3 +339,82 @@ func TestCheckWindowsFailsWhenStatusScanFails(t *testing.T) {
333
339
  t.Fatalf("status failure must be reported on stderr: err=%q", errBuf.String())
334
340
  }
335
341
  }
342
+
343
+ func TestRemovedTestHits(t *testing.T) {
344
+ diff := "diff --git a/old_test.go b/old_test.go\ndeleted file mode 100644\n--- a/old_test.go\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-package a\n-\tassert(true)\n" +
345
+ "diff --git a/a_test.go b/a_test.go\n--- a/a_test.go\n+++ b/a_test.go\n@@ -3,2 +3,0 @@\n-\tif got != 1 { t.Fatalf(\"got %d\", got) }\n-\trequire.NoError(t, err)\n" +
346
+ "diff --git a/moved.test.ts b/moved.test.ts\n--- a/moved.test.ts\n+++ b/moved.test.ts\n@@ -2 +2,0 @@\n- expect(x).toBe(1)\n@@ -9,0 +9 @@\n+ expect(x).toBe(1)\n" +
347
+ "diff --git a/lib.go b/lib.go\n--- a/lib.go\n+++ b/lib.go\n@@ -1 +0,0 @@\n-\tassert(ok)\n" +
348
+ "diff --git a/gone.go b/gone.go\ndeleted file mode 100644\n--- a/gone.go\n+++ /dev/null\n@@ -1 +0,0 @@\n-package a\n"
349
+ hits := removedTestHits(diff)
350
+ if len(hits) != 2 {
351
+ t.Fatalf("hits=%+v", hits)
352
+ }
353
+ if hits[0].Path != "old_test.go" || hits[0].Marker != "deleted-test" {
354
+ t.Fatalf("deleted test hit=%+v", hits[0])
355
+ }
356
+ if hits[1].Path != "a_test.go" || hits[1].Marker != "removed-assertion" || !strings.Contains(hits[1].Text, "net -2") {
357
+ t.Fatalf("removed assertion hit=%+v", hits[1])
358
+ }
359
+ }
360
+
361
+ func TestIsTestPath(t *testing.T) {
362
+ for p, want := range map[string]bool{
363
+ "pkg/a_test.go": true, "src/a.test.ts": true, "src/a.spec.js": true,
364
+ "tests/test_api.py": true, "spec/user_spec.rb": true, "src/__tests__/a.js": true,
365
+ "FooTest.java": true, "tests/integration.rs": true,
366
+ "pkg/a.go": false, "src/latest.ts": false, "tests/fixture.json": false, "contest.py": false,
367
+ } {
368
+ if got := isTestPath(p); got != want {
369
+ t.Fatalf("isTestPath(%q)=%v want %v", p, got, want)
370
+ }
371
+ }
372
+ }
373
+
374
+ func TestCheckWindowsBlocksLoweredBarUntilWaived(t *testing.T) {
375
+ project := t.TempDir()
376
+ initWindowsRepo(t, project)
377
+ root := windowsWorkspace(t, project, "feat")
378
+ testutil.WriteFile(t, filepath.Join(project, "a_test.go"), "package a\n\nfunc TestA(t *testing.T) {\n\tif A() != 1 {\n\t\tt.Fatal(\"bad\")\n\t}\n}\n")
379
+ testutil.WriteFile(t, filepath.Join(project, "b_test.go"), "package a\n")
380
+ runSecretScanGit(t, project, "add", "--", "a_test.go", "b_test.go")
381
+ runSecretScanGit(t, project, "commit", "-qm", "baseline")
382
+ testutil.WriteFile(t, filepath.Join(project, "a_test.go"), "package a\n\nfunc TestA(t *testing.T) {\n\t_ = A()\n}\n")
383
+ if err := os.Remove(filepath.Join(project, "b_test.go")); err != nil {
384
+ t.Fatal(err)
385
+ }
386
+
387
+ var out, errBuf bytes.Buffer
388
+ if code := RunCheckWindows(root, []string{"feat"}, &out, &errBuf); code != 3 {
389
+ t.Fatalf("code=%d out=%q err=%q", code, out.String(), errBuf.String())
390
+ }
391
+ for _, want := range []string{"unwaived: a_test.go removed-assertion", "unwaived: b_test.go deleted-test"} {
392
+ if !strings.Contains(out.String(), want) {
393
+ t.Fatalf("missing %q in out=%q", want, out.String())
394
+ }
395
+ }
396
+
397
+ testutil.WriteFile(t, filepath.Join(root, "work", "feat", "windows.md"),
398
+ "- a_test.go | removed-assertion | assertion moved to TestB in SLICE-002\n- b_test.go | deleted-test | feature removed per spec AC-3\n")
399
+ out.Reset()
400
+ if code := RunCheckWindows(root, []string{"feat"}, &out, &errBuf); code != 0 {
401
+ t.Fatalf("waived: code=%d out=%q", code, out.String())
402
+ }
403
+ }
404
+
405
+ func TestCheckWindowsRenamedTestIsNotDeleted(t *testing.T) {
406
+ project := t.TempDir()
407
+ initWindowsRepo(t, project)
408
+ root := windowsWorkspace(t, project, "feat")
409
+ runSecretScanGit(t, project, "config", "diff.renames", "false")
410
+ body := "package a\n\nfunc TestA(t *testing.T) {\n\tif A() != 1 {\n\t\tt.Fatal(\"bad\")\n\t}\n}\n"
411
+ testutil.WriteFile(t, filepath.Join(project, "a_test.go"), body)
412
+ runSecretScanGit(t, project, "add", "--", "a_test.go")
413
+ runSecretScanGit(t, project, "commit", "-qm", "baseline")
414
+ runSecretScanGit(t, project, "mv", "a_test.go", "b_test.go")
415
+
416
+ var out, errBuf bytes.Buffer
417
+ if code := RunCheckWindows(root, []string{"feat"}, &out, &errBuf); code != 0 {
418
+ t.Fatalf("rename flagged: code=%d out=%q err=%q", code, out.String(), errBuf.String())
419
+ }
420
+ }
package/engine/main.go CHANGED
@@ -48,11 +48,11 @@ Usage:
48
48
  devrites-engine orient <slug> Alias for observe summary
49
49
  devrites-engine next [slug] Print the minimal remaining lifecycle path with advisory skips
50
50
  devrites-engine handoff [slug] Emit the deterministic resume record (cursor, blocking gates, ledger, dead ends)
51
- devrites-engine context [slug] (--phase <p> | --skill <name>) [--role <r>] [--trigger a,b]
51
+ devrites-engine context [slug] (--phase <p> | --skill <name>) [--role <r>] [--slice <id>] [--trigger a,b]
52
52
  Emit one deduplicated read-set bundle for a phase, skill, or dispatch role
53
53
  devrites-engine metrics record <slug> --phase <p> --event <e> [--role r] [--bytes n] [--note s]
54
- devrites-engine metrics summary [slug] Summarize the per-feature metrics.jsonl event ledger
55
- devrites-engine dispatch <slug> <sub> Launch-wave barrier for parallel dispatch: open/start/seal/return/status/abandon
54
+ devrites-engine metrics summary [slug] [--by-slice] Summarize the metrics ledger, or per-slice rounds and agent-minutes
55
+ devrites-engine dispatch <slug> <sub> Launch-wave barrier for parallel dispatch: open/start/seal/return/status/abandon (build wright waves: --slice, round cap)
56
56
  devrites-engine claim <add|release|list|check> Advisory session-scoped file claims for same-tree concurrent sessions (.devrites/claims.jsonl)
57
57
  devrites-engine note <subcommand> <slug> Anchored workspace notes bound to verbatim code quotes; drift regrades them (notes.md)
58
58
  devrites-engine observe slice <slug> <SLICE-ID> Print one SLICE-### section of tasks.md
@@ -120,7 +120,10 @@ unclear boundary is underspecified: return `Escalation` without proceeding.
120
120
  new fingerprint. At the limit return the gate and repro. `Escalation` is only for product/irreversible choices
121
121
  or human-only access; technical failure is a blocker.
122
122
  Before handoff, inspect your cumulative slice diff against the contract and
123
- scenario matrix, including correction-affected adjacent variants. Fix supported
123
+ scenario matrix, including correction-affected adjacent variants, then against
124
+ the reviewers' own checks: [code review § What to check](../skills/devrites-lib/reference/standards/code-review.md#what-to-check-tests-first)
125
+ and test analysis (each acceptance criterion has a test that fails without the
126
+ change; no assertion-free, tautological, or mock-asserting test). Fix supported
124
127
  in-scope gaps together, rerun affected proof, and report remaining gaps. Self-check
125
128
  never replaces independent review; green tests do not prove all possible cases.
126
129
  5. **TRANSFER when isolated.** After green writer-safe proof, verify the diff again,
@@ -138,7 +141,9 @@ Stay inside the exact project-relative paths listed in the dispatch task.
138
141
  return an `Escalation`** when:
139
142
  - the slice is **underspecified**, the **plan looks wrong**, or requirements, code,
140
143
  and tests conflict;
141
- - the slice needs a **new dependency** or a **second design system**;
144
+ - the slice needs a **new dependency** or a **second design system**, or a planned
145
+ package fails to install or resolve (report the failure; never retry under a
146
+ similar or alternative package name);
142
147
  - the work requires an unapproved product/security-policy choice, public API break,
143
148
  external-service contract change, destructive live migration, or filesystem
144
149
  destruction outside the workspace. A path allowlist alone does not approve those
@@ -3,6 +3,12 @@
3
3
  Generate **3-5 ranked hypotheses** *before* testing any of them.
4
4
  Single-hypothesis generation anchors on the first plausible idea.
5
5
 
6
+ **Check past root causes first.** Search `.devrites/archive/*/evidence.md` with
7
+ native file search for the failure's error text, symbol, and file names. A hit
8
+ whose recorded correct hypothesis matches enters the ranked list with its
9
+ recurrence noted; it is still tested, never assumed. No archive or no hit →
10
+ skip silently.
11
+
6
12
  Each hypothesis must be **falsifiable**: state the prediction it makes.
7
13
 
8
14
  > Format: *"If <X> is the cause, then <changing Y> will make the failure
@@ -36,6 +36,7 @@ If the candidate diff has no matching surfaces, record
36
36
  2. Build each reviewer's read-set once with
37
37
  `devrites-engine context <slug> --phase <p> --role <role> --trigger <set>` and
38
38
  hand the bundle path as the packet's source list instead of a file roster.
39
+ Build roles add `--slice <SLICE-ID>` (slice-scoped, slice-named bundle).
39
40
  Evaluate the skill's trigger conditions and pass each that fires — a role's
40
41
  Trigger column maps to manifest names: `security-auditor` → `security`,
41
42
  `frontend-reviewer` → `ui`/`frontend`, `performance-reviewer` → `performance`.
@@ -59,7 +59,7 @@ topic's owner.
59
59
  | `architecture-health.md` | An audit needs a whole-codebase structural score from a code index — coupling, cohesion, cycles, god files, orphans, depth. |
60
60
  | `assumption-checkpoints.md` | Scope generalizes an existing boundary: a second case, a newly optional field, or a constant turned parameter. |
61
61
  | `debug-recovery.md` | Waiting on async readiness (server start, job completion, browser signal) needs a bounded condition-based poll. |
62
- | `windows.md` | A deferral marker (TODO/FIXME/skipped test/stub) is introduced or waived — the `check windows` ledger and grammar. |
62
+ | `windows.md` | A deferral or bar-lowering marker (TODO/FIXME/skipped test/stub/suppression/removed test) is introduced or waived — the `check windows` ledger and grammar. |
63
63
  | `glossary.md` | A DevRites term is unclear; resolve it once here instead of inferring. |
64
64
 
65
65
  The table is a load trigger, not an exemption. [`core.md` § Rule summary](core.md#rule-summary-load-the-full-file-when-in-scope)
@@ -357,6 +357,8 @@ access, safety and irreversible-action boundaries independently stop work.
357
357
  renaming/splitting an unchanged cause does not. Suggestion/Nit/FYI cannot extend
358
358
  recovery. Initial discovery and expected test-first RED are not corrections;
359
359
  total rounds, re-batches and distinct-finding counts never exhaust recovery.
360
+ Every 5 Build wright rounds on one slice, the next first needs a reslice review
361
+ ([`wright-dispatch.md`](../../../rite-build/reference/wright-dispatch.md#run)).
360
362
  - **Rechecks are scoped; late findings never reopen the round.** *Late* applies only
361
363
  from a phase's **second** review pass (Vet 1b, Build's recheck after the first repair,
362
364
  Review step 7); an initial pass has no late findings and every supported finding is
@@ -30,6 +30,9 @@ If it does not, do not merge it.
30
30
  no justification), stripped assertions, deleted or skipped tests, and new
31
31
  unimplemented stubs or `throw`-only placeholders. Each names what it lowers and
32
32
  needs a recorded reason in the diff or `decisions.md`; unexplained is a finding.
33
+ `devrites-engine check windows` flags the mechanical subset (suppressions,
34
+ skip/focus, stubs, empty catch, deleted tests, stripped assertions) against
35
+ `windows.md` waivers; loosened thresholds remain this sweep's judgment.
33
36
  3. **Correctness:** logic, edge cases, error paths, race conditions, wrong assumptions. For branching or boundary changes, run the [`edge-case trace`](edge-case-trace.md): relevant probe classes, fixed-set siblings, real wiring, negative intent, and deletion contracts with an evidence disposition.
34
37
  4. **Readability:** names, function size, control flow, intent obvious without the author.
35
38
  5. **Architecture:** right seam, coupling/cohesion, fits existing patterns, no premature
@@ -35,6 +35,6 @@
35
35
  | Standards | The rule files in this directory; `core.md` is always-on, the rest load on trigger (`README.md` table). |
36
36
  | Transfer commit | The isolated wright's local unpushed `WIP(<slug>):` transport commit; never the control-branch checkpoint. |
37
37
  | Trigger | A named conditional read-set in a `loads:` manifest (e.g. `tdd`, `applicability`, `ui`); engine fails closed on unknown names. |
38
- | Windows ledger | `windows.md`: one waiver row per deferral marker the change introduces; `check windows` fails on unwaived hits. [`windows.md`](windows.md). |
38
+ | Windows ledger | `windows.md`: one waiver row per deferral or bar-lowering marker the change introduces; `check windows` fails on unwaived hits. [`windows.md`](windows.md). |
39
39
  | Workflow Artifact | A Vet-ready, root-authored executable file under `.devrites/work/<slug>/`; [`workflow-artifacts.md`](workflow-artifacts.md). |
40
40
  | Wright | `devrites-slice-wright`: the sole product source/test writer under a path-bounded contract. |
@@ -96,6 +96,7 @@ On any security-relevant error: deny, roll back; never default to allow or half-
96
96
  - Install reproducibly from a committed lockfile (`npm ci` / frozen); never resolving installs in CI. Hand-editing lockfiles bypasses review.
97
97
  - Distrust install scripts (`postinstall` runs arbitrary code) — review before adding; prefer `--ignore-scripts`.
98
98
  - Typosquats are a delivery vector: confirm exact name/publisher, not install success.
99
+ After a failed install, never try a similarly named package; report the failure.
99
100
  - A CI/release change names the SLSA build provenance level it claims and where the build runs; a claimed level with no verified provenance attestation is `cannot_verify`, not a pass.
100
101
 
101
102
  ## Trust boundary (three tiers)