devrites 7.0.8 → 7.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to DevRites are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and DevRites adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are generated automatically by [semantic-release](https://semantic-release.gitbook.io/) from Conventional Commits on `main`.
4
4
 
5
+ ## [7.0.10](https://github.com/ViktorsBaikers/DevRites/compare/v7.0.9...v7.0.10) (2026-10-23)
6
+
7
+ ### Fixed
8
+
9
+ * **devrites:** apply-bundle refuses an unrunnable preflight row ([#166](https://github.com/ViktorsBaikers/DevRites/issues/166)) ([6bc06a4](https://github.com/ViktorsBaikers/DevRites/commit/6bc06a40530e3456b80883303e345fb25d45357a))
10
+
11
+ ## [7.0.9](https://github.com/ViktorsBaikers/DevRites/compare/v7.0.8...v7.0.9) (2026-10-22)
12
+
13
+ ### Fixed
14
+
15
+ * **devrites:** keep a passed pre-dispatch row on re-dispatch ([#165](https://github.com/ViktorsBaikers/DevRites/issues/165)) ([927f5a4](https://github.com/ViktorsBaikers/DevRites/commit/927f5a482cfb70c84255a9ead467251483de7fee))
16
+
5
17
  ## [7.0.8](https://github.com/ViktorsBaikers/DevRites/compare/v7.0.7...v7.0.8) (2026-10-22)
6
18
 
7
19
  ### Fixed
package/README.md CHANGED
@@ -31,7 +31,7 @@ history can't lose it. After you clear the conversation, switch hosts, or hand
31
31
  the feature to another engineer, the next session reads the same plan,
32
32
  decisions, and evidence before it continues.
33
33
 
34
- **Status:** [`v7.0.8`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v7.0.8): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
34
+ **Status:** [`v7.0.10`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v7.0.10): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
35
35
 
36
36
  This is the latest published release; `main` may contain unreleased work.
37
37
 
package/docs/cli.md CHANGED
@@ -128,8 +128,9 @@ self-contained updater.
128
128
  - `check readiness --emit-binding <slug>` renders the exact stable Build-input
129
129
  binding for Vet to record after semantic review.
130
130
  - `check preconditions <slug>` runs the preflight rows whose `Run at` is `vet`
131
- (`--slice <SLICE>` runs that slice's `pre-dispatch` rows) and records a
132
- candidate-bound transcript per row; exit 3 on failure.
131
+ (`--slice <SLICE>` runs that slice's `pre-dispatch` rows, keeping one that
132
+ already passed) and records a candidate-bound transcript per row; exit 3 on
133
+ failure.
133
134
  - `check seal <slug>` checks final required files, open human gates, and the
134
135
  `tasks.md` slice graph. Once those files are complete, it verifies the stable
135
136
  readiness binding; only after that aggregate check passes does it verify that
@@ -14,7 +14,7 @@ workflow strategy.
14
14
  | `check candidate <slug>` | Validate the strict manifest and compute the content-bound project-candidate identity. Exits `3` when the manifest omits a path the feature's `WIP(<slug>):` checkpoint commits changed (net from the feature base to HEAD). Also exits `3` with `unattributed root edit: <paths>` while the feature's `tree-baseline.json` tripwire flags a product path (ADR-0038; see `dispatch`). |
15
15
  | `check readiness <slug>` | Check target-Phase files, open human gates from Clarify onward, the `tasks.md` slice graph when that artifact is required, canonical `AC-###` presence in `tasks.md`/`test-plan.md` when those artifacts are required, and the current stable Build-input binding when applicable. Leaving Vet with a `Run at` column, a slice whose `Files likely touched`/`Writer allowlist` names a dependency manifest or lockfile needs a `dependency-dry-run SLICE-###` row at `vet` with a passing transcript. Leaving Vet, every slice `state.md` does not check built (a deferred `Satisfies: none` slice excepted) must also pass the Build-entry `check slice` contract; each failure prints one `invariant: slice-check:` line naming the slice and its exact `check slice` reason, to fix in `/rite-plan` before READY. |
16
16
  | `check readiness --emit-binding <slug>` | Render the exact stable Build-input binding for Vet to record after review. |
17
- | `check preconditions <slug> [--slice SLICE-###] [--timeout N]` | Run the `test-plan.md` Build-entry preflight rows whose optional `Run at` cell is `vet` (with `--slice`, `pre-dispatch <SLICE>`); `action` rows and rows without the cell never run. Writes one candidate-bound transcript per row under `references/gates/preflight-*/`; a row passes on exit 0. Exit `3` on any failure or an invalid `Run at` value. |
17
+ | `check preconditions <slug> [--slice SLICE-###] [--timeout N]` | Run the `test-plan.md` Build-entry preflight rows whose optional `Run at` cell is `vet` (with `--slice`, `pre-dispatch <SLICE>`); `action` rows and rows without the cell never run. Writes one candidate-bound transcript per row under `references/gates/preflight-*/`; a row passes on exit 0. A `pre-dispatch` row whose newest transcript passed is kept, not re-run: it is a pre-edit baseline, so a re-dispatch never runs it against the slice's own edits; changing its command or cwd retires the pass. Exit `3` on any failure or an invalid `Run at` value. |
18
18
  | `check seal <slug>` | Check files required by target Phase `seal`, open human gates, the `tasks.md` slice graph, canonical `AC-###` presence when `tasks.md`/`test-plan.md` are required, the stable Build-input binding, and exact candidate bindings. `check readiness` and `check seal` also block a `scripts/` test driver that a tasks.md Writer allowlist plans or touched-files.md lists when no gates.md `CHECK` runs it and no `Driver waiver: <path> — DEC-### <reason>` line cites a recorded decision; waived drivers print as `note: unexecuted driver (waived by DEC-###): <path>` lines, on pass or block. |
19
19
  | `check path-disjoint [--root <dir>] [<json-file> | -]` | Verify slice path sets are pairwise disjoint. |
20
20
  | `parallel select --cap <1-10> [--root <dir>] [--slug <slug>] [--from-plan \| <json-file> \| -]` | Choose the greedy path-disjoint subset ≤ cap from supplied ready slices, or with `--from-plan` (needs `--slug`) from the plan: tasks.md slices whose dependencies state.md checks `built`, with Writer allowlist (else Files likely touched) paths plus the nearest manifest/lockfile when `External dependencies:` is not none. With `--slug` and a running lease, the lease's not-yet-landed slices hold cap slots and paths, slices it names are never picked, and only new picks print. |
@@ -1,6 +1,7 @@
1
1
  package acceptance
2
2
 
3
3
  import (
4
+ "fmt"
4
5
  "path/filepath"
5
6
  "strings"
6
7
 
@@ -25,6 +26,9 @@ type PreflightRow struct {
25
26
  // RunAt is the optional `Run at` cell (`vet`, `pre-dispatch SLICE-###`,
26
27
  // `action`), whitespace-collapsed; "" when the row or table has none.
27
28
  RunAt string
29
+ // Width and HeaderWidth are the row's and its table header's cell counts;
30
+ // they differ when a bare `|` in a command splits the row.
31
+ Width, HeaderWidth int
28
32
  }
29
33
 
30
34
  // ApprovedCommands extracts the approved command surface of test-plan.md: the
@@ -56,7 +60,7 @@ func PreflightRows(testPlan []byte) (rows []PreflightRow, hasRunAt bool) {
56
60
  return nil, false
57
61
  }
58
62
  inSection := false
59
- commandCol, cwdCol, gateCol, expectedCol, runAtCol := -1, -1, -1, -1, -1
63
+ commandCol, cwdCol, gateCol, expectedCol, runAtCol, headerWidth := -1, -1, -1, -1, -1, 0
60
64
  for _, line := range strings.Split(string(structural), "\n") {
61
65
  trimmed := strings.TrimSpace(line)
62
66
  if strings.HasPrefix(trimmed, "##") {
@@ -77,6 +81,7 @@ func PreflightRows(testPlan []byte) (rows []PreflightRow, hasRunAt bool) {
77
81
  gateCol = columnIndex(cells, "gate")
78
82
  expectedCol = columnIndex(cells, "expected")
79
83
  runAtCol = columnIndex(cells, "run at")
84
+ headerWidth = len(cells)
80
85
  hasRunAt = hasRunAt || (commandCol >= 0 && runAtCol >= 0)
81
86
  continue
82
87
  }
@@ -87,7 +92,7 @@ func PreflightRows(testPlan []byte) (rows []PreflightRow, hasRunAt bool) {
87
92
  if command == "" {
88
93
  continue
89
94
  }
90
- row := PreflightRow{ApprovedCommand: ApprovedCommand{Command: command}}
95
+ row := PreflightRow{ApprovedCommand: ApprovedCommand{Command: command}, Width: len(cells), HeaderWidth: headerWidth}
91
96
  if cwdCol >= 0 && cwdCol < len(cells) {
92
97
  row.Cwd = normalizeApprovedCwd(cells[cwdCol])
93
98
  }
@@ -99,6 +104,30 @@ func PreflightRows(testPlan []byte) (rows []PreflightRow, hasRunAt bool) {
99
104
  return rows, hasRunAt
100
105
  }
101
106
 
107
+ // PreflightShapeProblems names each Build-entry preflight row that cannot run
108
+ // as written: a cell count that differs from its header (an unescaped `|`
109
+ // in the command, which must be written `\|`) or an invalid `Run at` value.
110
+ // `check preconditions` would otherwise find it only when Vet runs the row.
111
+ func PreflightShapeProblems(testPlan []byte) []string {
112
+ rows, _ := PreflightRows(testPlan)
113
+ var problems []string
114
+ for _, row := range rows {
115
+ name := valueOr(row.Gate, row.Command)
116
+ if row.Width != row.HeaderWidth {
117
+ problems = append(problems, fmt.Sprintf("preflight row %q has %d cells but its header has %d; escape each `|` in the command as `\\|`", name, row.Width, row.HeaderWidth))
118
+ } else if !ValidRunAt(row.RunAt) {
119
+ problems = append(problems, fmt.Sprintf("preflight row %q has invalid Run at %q (want vet, pre-dispatch SLICE-###, or action)", name, row.RunAt))
120
+ }
121
+ }
122
+ return problems
123
+ }
124
+
125
+ // ValidRunAt reports whether a `Run at` cell is empty or a known value.
126
+ func ValidRunAt(runAt string) bool {
127
+ lower := strings.ToLower(runAt)
128
+ return lower == "" || lower == "vet" || lower == "action" || preDispatchRunAtRE.MatchString(runAt)
129
+ }
130
+
102
131
  // cellText returns a cell's trimmed text without code backticks, "" when the
103
132
  // column is absent or the row is short.
104
133
  func cellText(cells []string, col int) string {
@@ -77,7 +77,9 @@ func PreflightPassed(work string, row PreflightRow) bool {
77
77
  // RunPreconditions is `check preconditions <slug> [--slice S]`: it executes
78
78
  // the Build-entry preflight rows whose `Run at` is `vet` (or, with --slice,
79
79
  // `pre-dispatch <S>`), writes one candidate-bound transcript per row, and exits
80
- // ExitBlocked on any failure. Rows are the approved surface itself, so nothing
80
+ // ExitBlocked on any failure. A pre-dispatch row that already passed keeps that
81
+ // transcript: it is a pre-edit baseline, and a repair round's re-dispatch would
82
+ // otherwise re-run it against the slice's own unfinished edits. Rows are the approved surface itself, so nothing
81
83
  // outside test-plan.md runs; `action` rows and rows without a `Run at` cell
82
84
  // never run here.
83
85
  func RunPreconditions(root string, args []string, stdout, stderr io.Writer) int {
@@ -157,12 +159,11 @@ func RunPreconditions(root string, args []string, stdout, stderr io.Writer) int
157
159
  // and whether any row ran.
158
160
  func runPreflightRows(root, slug, work, project, runAt string, rows []PreflightRow, timeout time.Duration, stdout io.Writer) (bool, bool, error) {
159
161
  before := candidateLabel(root, slug)
160
- failed := false
162
+ failed, kept := false, false
161
163
  doc := &Document{}
162
164
  var results []GateResult
163
165
  for _, row := range rows {
164
- lower := strings.ToLower(row.RunAt)
165
- if lower != "" && lower != "vet" && lower != "action" && !preDispatchRunAtRE.MatchString(row.RunAt) {
166
+ if !ValidRunAt(row.RunAt) {
166
167
  fmt.Fprintf(stdout, "row %s: invalid Run at %q (want vet, pre-dispatch SLICE-###, or action)\n", valueOr(row.Gate, row.Command), row.RunAt)
167
168
  failed = true
168
169
  continue
@@ -170,6 +171,11 @@ func runPreflightRows(root, slug, work, project, runAt string, rows []PreflightR
170
171
  if !strings.EqualFold(row.RunAt, runAt) {
171
172
  continue
172
173
  }
174
+ if preDispatchRunAtRE.MatchString(runAt) && PreflightPassed(work, row) {
175
+ fmt.Fprintf(stdout, "row %s: pass (kept; a pre-dispatch row runs once, before the slice's first edit)\n", PreflightGateID(row))
176
+ kept = true
177
+ continue
178
+ }
173
179
  gate := &Gate{ID: PreflightGateID(row), Title: row.Gate, Check: row.Command, Cwd: row.Cwd,
174
180
  Expect: "exit 0 (Expected: " + valueOr(row.Expected, "unstated") + ")"}
175
181
  cwd := project
@@ -197,5 +203,5 @@ func runPreflightRows(root, slug, work, project, runAt string, rows []PreflightR
197
203
  return failed, true, fmt.Errorf("transcript: %w", err)
198
204
  }
199
205
  }
200
- return failed, len(results) > 0, nil
206
+ return failed, kept || len(results) > 0, nil
201
207
  }
@@ -15,6 +15,7 @@ import (
15
15
  "sort"
16
16
  "strings"
17
17
 
18
+ "github.com/devrites/devrites/internal/acceptance"
18
19
  "github.com/devrites/devrites/internal/devritespaths"
19
20
  "github.com/devrites/devrites/internal/fsutil"
20
21
  "github.com/devrites/devrites/internal/state"
@@ -360,7 +361,8 @@ func applyHunks(base string, hunks []candidateHunk) (string, []string) {
360
361
 
361
362
  // checkBundleResult rejects an outcome the root would otherwise discover only
362
363
  // at Vet: a tracked ID that vanished from every touched file without being
363
- // listed in retired_ids, a new tasks.md graph problem, or a budgeted artifact
364
+ // listed in retired_ids, a new tasks.md graph problem, a new test-plan.md
365
+ // preflight row that cannot run as written, or a budgeted artifact
364
366
  // that grew into material overshoot without a covering
365
367
  // `Budget override: <=N lines; <reason>` line.
366
368
  func checkBundleResult(targets []*bundleTarget, retired map[string]bool) []string {
@@ -400,6 +402,17 @@ func checkBundleResult(targets []*bundleTarget, retired map[string]bool) []strin
400
402
  }
401
403
  }
402
404
  }
405
+ if name == "test-plan.md" && filepath.Dir(target.rel) == "." {
406
+ old := map[string]bool{}
407
+ for _, p := range acceptance.PreflightShapeProblems(target.before) {
408
+ old[p] = true
409
+ }
410
+ for _, p := range acceptance.PreflightShapeProblems(target.after) {
411
+ if !old[p] {
412
+ problems = append(problems, "test-plan.md: "+p)
413
+ }
414
+ }
415
+ }
403
416
  if filepath.Dir(target.rel) == "." && currentStatePlanning[name] &&
404
417
  len(datedHeadingRE.FindAllIndex(target.after, -1)) > len(datedHeadingRE.FindAllIndex(target.before, -1)) {
405
418
  problems = append(problems, target.rel+": adds a dated delta section; rewrite the owning clause in place (rationale to decisions.md, closed narrative to history/)")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devrites",
3
- "version": "7.0.8",
3
+ "version": "7.0.10",
4
4
  "description": "DevRites: a disciplined senior-engineer workflow pack for Claude Code, Codex, omp, pi, and Devin CLI",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://github.com/ViktorsBaikers/DevRites#readme",