devrites 6.7.4 → 6.8.0

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 (29) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/docs/engine/commands.md +3 -3
  4. package/engine/internal/acceptance/evidence.go +28 -5
  5. package/engine/internal/acceptance/inputs.go +93 -0
  6. package/engine/internal/acceptance/ledger.go +5 -2
  7. package/engine/internal/acceptance/run.go +18 -5
  8. package/engine/internal/lib/candidate_coverage.go +147 -0
  9. package/engine/internal/lib/dispatch.go +5 -0
  10. package/engine/main.go +4 -0
  11. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +2 -1
  12. package/pack/.claude/skills/devrites-lib/reference/standards/gates.md +16 -7
  13. package/pack/.claude/skills/rite-review/SKILL.md +6 -1
  14. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +2 -1
  15. package/pack/generated/claude/skills/devrites-lib/reference/standards/gates.md +16 -7
  16. package/pack/generated/claude/skills/rite-review/SKILL.md +6 -1
  17. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +2 -1
  18. package/pack/generated/codex/skills/devrites-lib/reference/standards/gates.md +16 -7
  19. package/pack/generated/codex/skills/rite-review/SKILL.md +6 -1
  20. package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +2 -1
  21. package/pack/generated/devin/skills/devrites-lib/reference/standards/gates.md +16 -7
  22. package/pack/generated/devin/skills/rite-review/SKILL.md +6 -1
  23. package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +2 -1
  24. package/pack/generated/omp/skills/devrites-lib/reference/standards/gates.md +16 -7
  25. package/pack/generated/omp/skills/rite-review/SKILL.md +6 -1
  26. package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +2 -1
  27. package/pack/generated/pi/skills/devrites-lib/reference/standards/gates.md +16 -7
  28. package/pack/generated/pi/skills/rite-review/SKILL.md +6 -1
  29. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to DevRites are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and DevRites adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are generated automatically by [semantic-release](https://semantic-release.gitbook.io/) from Conventional Commits on `main`.
4
4
 
5
+ ## [6.8.0](https://github.com/ViktorsBaikers/DevRites/compare/v6.7.4...v6.8.0) (2026-10-18)
6
+
7
+ ### Added
8
+
9
+ * **devrites:** reuse unchanged gates and refuse voided review waves ([#129](https://github.com/ViktorsBaikers/DevRites/issues/129)) ([51349fe](https://github.com/ViktorsBaikers/DevRites/commit/51349fe0ce8f96ad890461c0e796bf3184e49079))
10
+
5
11
  ## [6.7.4](https://github.com/ViktorsBaikers/DevRites/compare/v6.7.3...v6.7.4) (2026-10-17)
6
12
 
7
13
  ### 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:** [`v6.7.4`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v6.7.4): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
34
+ **Status:** [`v6.8.0`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v6.8.0): 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
 
@@ -11,7 +11,7 @@ workflow strategy.
11
11
  | `install [flags]` | Install manifest-owned host artifacts and the optional shared binary. |
12
12
  | `update [flags]` | Refresh an existing managed installation. |
13
13
  | `uninstall [flags]` | Remove managed artifacts while preserving runtime workspace state. |
14
- | `check candidate <slug>` | Validate the strict manifest and compute the content-bound project-candidate identity. |
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). |
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. |
16
16
  | `check readiness --emit-binding <slug>` | Render the exact stable Build-input binding for Vet to record after review. |
17
17
  | `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. |
@@ -34,12 +34,12 @@ workflow strategy.
34
34
  | `handoff [slug]` | Emit the deterministic resume record: cursor fields, `awaiting_human`, open `questions.md` gate kinds, the `gates.md` reduction with unmet/stale/abandoned ids, `decisions.md` dead ends, missing required files, and the canonical read-next order. Read-only; the `/rite-handoff` spine and the compaction-fallback entry point. |
35
35
  | `recall --paths <p1,p2,...> [--limit n]` | Read-time query over `.devrites/archive/`: list archived features whose `touched-files.md` candidate manifest covers a queried project-relative path (exact or directory prefix, case-folded), ranked by overlap count, then newest archive-directory mtime, then slug (default 10 features). Each feature prints up to 8 `archive/<slug>/<file>:<line>` pointers, then `+N more`: `decisions.md` `DEC-###` entries and `## Carry-forward constraints` bullets whose `Files:` overlap (or a constraint naming no files), plus `review.md`/`seal.md`/`drift.md` lines that mention a queried path; fenced examples are ignored. Never writes, caches, or indexes (no metric either); symlinked archive entries are refused. An unreadable archive file prints `unreadable:` and the scan continues. Exit `0` (including `recall: no archive`), `1` invalid archive directory, `2` usage. |
36
36
  | `context [slug] (--phase <p>\|--skill <name>) [--role <r>] [--slice <SLICE-ID>] [--trigger a,b]` | Emit one deduplicated read-set bundle per phase/role/skill from each skill's `loads:` manifest; `--skill` works without a workspace when `--out` is given. A bundle without `--role` (the root's own read) gives over-budget artifacts as by-reference indexes; a `--role` bundle leads with the role's agent contract and names the manifest's `rootOnly` paths instead of inlining them. `--slice` inlines that slice's `tasks.md` block and turns each over-budget artifact, and each artifact over 16 KiB except `spec.md`, into a by-reference index (path, SHA-256, headings, line hits for every ID the block cites); repeat `--slice` for several slices; an unknown slice exits `3`. |
37
- | `dispatch <slug> <open\|start\|seal\|return\|status\|abandon>` | Launch-wave barrier for parallel dispatch: seal needs a handle per role, return needs seal; start/return auto-record metrics. A build wave with a wright role needs `--slice <SLICE-ID>` per slice, and a slice-bound vet wave takes it too; a reviewer, auditor, or test-analyst wave needs `--packet <file>` and `open` exits `3` on a packet that seeds a conclusion (gate `*-PASS` token, `no-findings`, an `Outcome:`/`Counts:` line, an "already passed" claim, or zero Critical/Important counts); every 5 rounds of either kind on one slice, `open` exits `3` until `--reslice-review <DRIFT-ID>` names an unused `drift.md` `## <DRIFT-ID>` heading for that slice. `open` also warns when an earlier wave for the same slice never returned. |
37
+ | `dispatch <slug> <open\|start\|seal\|return\|status\|abandon>` | Launch-wave barrier for parallel dispatch: seal needs a handle per role, return needs seal; start/return auto-record metrics. A build wave with a wright role needs `--slice <SLICE-ID>` per slice, and a slice-bound vet wave takes it too; a reviewer, auditor, or test-analyst wave needs `--packet <file>` and `open` exits `3` on a packet that seeds a conclusion (gate `*-PASS` token, `no-findings`, an `Outcome:`/`Counts:` line, an "already passed" claim, or zero Critical/Important counts); a Review or Seal reviewer wave also exits `3` unless the packet names an existing non-empty `.diff`/`.patch` file and the manifest covers every checkpointed path; every 5 rounds of either kind on one slice, `open` exits `3` until `--reslice-review <DRIFT-ID>` names an unused `drift.md` `## <DRIFT-ID>` heading for that slice. `open` also warns when an earlier wave for the same slice never returned. |
38
38
  | `metrics record <slug> --phase <p> --event <e> [--role r] [--bytes n] [--note s]` | Append an event to the workspace's `metrics.jsonl` ledger. |
39
39
  | `metrics summary [slug] [--by-slice]` | Reduce `metrics.jsonl` into a per-phase roll-up. `--by-slice` reads `dispatch.json` instead: per slice, build wright rounds, slice-bound Vet rechecks, and agent-minutes (role start to `returned_at`; abandoned waves excluded). |
40
40
  | `claim <add\|release\|list\|check>` | Advisory session-scoped file claims in `.devrites/claims.jsonl` (append-only). `add` records intent to write a path set with a TTL (1–240 min, default 30); a live claim by another session exits `3` and names the holder. `release` appends a release event owned by the claiming session. `list` shows live claims (`--all` includes expired/released); a claim whose holding session has provably exited on this host shows `dead` and no longer conflicts. `check` is the read-only preflight for a path set. Coordination aid only — never a substitute for one-writer-per-worktree. |
41
41
  | `note <add\|list\|check\|rm> <slug> ...` | Anchored workspace notes in `notes.md`: `add <slug> <subject> <quote> <title> [body]` binds a note to a verbatim quote in one repository file and assigns `NOTE-###`; `list` and `check` regrade each anchor `exact`/`moved`/`stale`/`ambiguous`/`lost` by searching the repo for the quote (dependency and workspace trees excluded); `check --repair` rewrites a `moved` subject to its new file; `rm` deletes by ID. `check seal` refuses a malformed `notes.md` and any non-`exact` anchor. |
42
- | `gates <scaffold\|status\|run\|reverify\|lint\|attest\|abandon> <slug>` | Operate the `gates.md` acceptance ledger: seed per-AC gates, execute runnable gates bound to `test-plan.md` preflight rows, attest manual gates, and reduce to `all-met`/`not-met`/`handoff`/`malformed`. |
42
+ | `gates <scaffold\|status\|run\|reverify\|lint\|attest\|abandon> <slug>` | Operate the `gates.md` acceptance ledger: seed per-AC gates, execute runnable gates bound to `test-plan.md` preflight rows, attest manual gates, and reduce to `all-met`/`not-met`/`handoff`/`malformed`. A gate with `INPUTS:` records its files' content digest and is reused by `run` and `reverify` while they are unchanged. |
43
43
  | `state resolve <qid> "<answer>"` | Resolve an open question and update `questions.md` plus `state.md` atomically. |
44
44
  | `state merge-manifest <slug> [pred...]` | Fold the recorded predecessor chain's manifests into the release candidate manifest. |
45
45
  | `state close <slug>` | Archive a shipped workspace and clear matching `ACTIVE`. |
@@ -25,14 +25,19 @@ const (
25
25
  // manual gate keeps human attestation.
26
26
  const EvidenceMarker = "automatic-evidence=v1"
27
27
 
28
- var automaticEvidenceRE = regexp.MustCompile(`^automatic-evidence=v1; def=([0-9a-f]{64}); exit=0; expect=matched; output-sha256=([0-9a-f]{64}); output-bytes=([0-9]+); at=(\S+)$`)
28
+ var automaticEvidenceRE = regexp.MustCompile(`^automatic-evidence=v1; def=([0-9a-f]{64}); exit=0; expect=matched; output-sha256=([0-9a-f]{64}); output-bytes=([0-9]+)(?:; inputs=([0-9a-f]{64}))?; at=(\S+)$`)
29
29
 
30
30
  // DefinitionDigest is the environment-independent SHA-256 binding of the
31
- // parsed CHECK, EXPECT, and raw CWD fields. Editing any bound input renders
32
- // previously recorded evidence stale. The digest is unkeyed drift detection,
33
- // not tamper proof: a ledger editor can write a syntactically valid line.
31
+ // parsed CHECK, EXPECT, raw CWD and (when declared) INPUTS fields. Editing any
32
+ // bound input renders previously recorded evidence stale. The digest is unkeyed
33
+ // drift detection, not tamper proof: a ledger editor can write a syntactically
34
+ // valid line.
34
35
  func DefinitionDigest(gate *Gate) string {
35
- sum := sha256.Sum256([]byte("v1\x00" + gate.Check + "\x00" + gate.Expect + "\x00" + gate.Cwd))
36
+ definition := "v1\x00" + gate.Check + "\x00" + gate.Expect + "\x00" + gate.Cwd
37
+ if gate.Inputs != "" {
38
+ definition += "\x00inputs\x00" + gate.Inputs
39
+ }
40
+ sum := sha256.Sum256([]byte(definition))
36
41
  return hex.EncodeToString(sum[:])
37
42
  }
38
43
 
@@ -42,6 +47,24 @@ func FormatEvidence(gate *Gate, outputDigest string, outputBytes int, at string)
42
47
  EvidenceMarker, DefinitionDigest(gate), outputDigest, outputBytes, at)
43
48
  }
44
49
 
50
+ // withInputs adds the INPUTS content digest to an evidence payload, so a later
51
+ // run can tell whether the files the result depends on are unchanged.
52
+ func withInputs(evidence, inputsDigest string) string {
53
+ if inputsDigest == "" {
54
+ return evidence
55
+ }
56
+ at := strings.LastIndex(evidence, "; at=")
57
+ return evidence[:at] + "; inputs=" + inputsDigest + evidence[at:]
58
+ }
59
+
60
+ // recordedInputs is the INPUTS digest a gate's automatic evidence carries, or "".
61
+ func recordedInputs(gate *Gate) string {
62
+ if match := automaticEvidenceRE.FindStringSubmatch(strings.TrimSpace(gate.Evidence)); match != nil {
63
+ return match[4]
64
+ }
65
+ return ""
66
+ }
67
+
45
68
  // OutputDigest is the recorded fingerprint of the canonical successful output
46
69
  // string. Raw output is never persisted.
47
70
  func OutputDigest(output string) string {
@@ -0,0 +1,93 @@
1
+ package acceptance
2
+
3
+ import (
4
+ "crypto/sha256"
5
+ "encoding/hex"
6
+ "errors"
7
+ "fmt"
8
+ "io"
9
+ "io/fs"
10
+ "os"
11
+ "path"
12
+ "path/filepath"
13
+ "sort"
14
+ "strings"
15
+ )
16
+
17
+ // maxInputFiles bounds one INPUTS expansion so a broad glob cannot turn a
18
+ // gate's reuse check into a repository-wide hash.
19
+ const maxInputFiles = 20000
20
+
21
+ // InputsDigest hashes every regular file an INPUTS spec names under project:
22
+ // whitespace-separated project-relative paths, directories (walked
23
+ // recursively, .git skipped) or globs. Each pattern must match at least one
24
+ // file, so a typo fails closed (the gate re-executes) instead of hashing
25
+ // nothing. The digest binds each sorted relative path to its content.
26
+ func InputsDigest(project, spec string) (string, error) {
27
+ files := map[string]bool{}
28
+ for _, pattern := range strings.Fields(spec) {
29
+ clean := path.Clean(pattern)
30
+ if path.IsAbs(clean) || clean == ".." || strings.HasPrefix(clean, "../") || strings.Contains(pattern, `\`) {
31
+ return "", fmt.Errorf("INPUTS %q must be a project-relative slash path", pattern)
32
+ }
33
+ matches, err := filepath.Glob(filepath.Join(project, filepath.FromSlash(clean)))
34
+ if err != nil {
35
+ return "", fmt.Errorf("INPUTS %q: %w", pattern, err)
36
+ }
37
+ before := len(files)
38
+ for _, match := range matches {
39
+ if err := addInputFiles(project, match, files); err != nil {
40
+ return "", err
41
+ }
42
+ }
43
+ if len(files) == before {
44
+ return "", fmt.Errorf("INPUTS %q matches no file", pattern)
45
+ }
46
+ if len(files) > maxInputFiles {
47
+ return "", fmt.Errorf("INPUTS names more than %d files", maxInputFiles)
48
+ }
49
+ }
50
+ names := make([]string, 0, len(files))
51
+ for name := range files {
52
+ names = append(names, name)
53
+ }
54
+ sort.Strings(names)
55
+ digest := sha256.New()
56
+ for _, name := range names {
57
+ file, err := os.Open(filepath.Join(project, filepath.FromSlash(name))) // #nosec G304 -- path enumerated under the project root
58
+ if err != nil {
59
+ return "", err
60
+ }
61
+ content := sha256.New()
62
+ _, err = io.Copy(content, file)
63
+ _ = file.Close()
64
+ if err != nil {
65
+ return "", err
66
+ }
67
+ fmt.Fprintf(digest, "%d:%s\x00%x\n", len(name), name, content.Sum(nil))
68
+ }
69
+ return hex.EncodeToString(digest.Sum(nil)), nil
70
+ }
71
+
72
+ func addInputFiles(project, start string, files map[string]bool) error {
73
+ return filepath.WalkDir(start, func(name string, entry fs.DirEntry, err error) error {
74
+ if err != nil {
75
+ return err
76
+ }
77
+ if entry.IsDir() {
78
+ if entry.Name() == ".git" {
79
+ return filepath.SkipDir
80
+ }
81
+ return nil
82
+ }
83
+ if !entry.Type().IsRegular() {
84
+ return nil
85
+ }
86
+ rel, err := filepath.Rel(project, name)
87
+ if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
88
+ return errors.New("INPUTS path escapes the project")
89
+ }
90
+ files[filepath.ToSlash(rel)] = true
91
+ return nil
92
+ })
93
+ }
@@ -30,6 +30,7 @@ type Gate struct {
30
30
  Check string
31
31
  Expect string
32
32
  Cwd string
33
+ Inputs string // optional whitespace-separated paths/globs the result depends on
33
34
  Evidence string
34
35
  Line int // 1-based line of the "- [ ]" row in the original text
35
36
  }
@@ -46,8 +47,8 @@ type Document struct {
46
47
 
47
48
  var (
48
49
  gateLineRE = regexp.MustCompile(`^\s*- \[([ xX])\] ([^\s:]+): (.*)$`)
49
- attrLineRE = regexp.MustCompile(`^(\s+)(CHECK|EXPECT|CWD|EVIDENCE):(.*)$`)
50
- unindentedAttr = regexp.MustCompile(`^(CHECK|EXPECT|CWD|EVIDENCE):`)
50
+ attrLineRE = regexp.MustCompile(`^(\s+)(CHECK|EXPECT|CWD|INPUTS|EVIDENCE):(.*)$`)
51
+ unindentedAttr = regexp.MustCompile(`^(CHECK|EXPECT|CWD|INPUTS|EVIDENCE):`)
51
52
  abandonLineRE = regexp.MustCompile(`^ABANDON: (\S+)\s*(.*)$`)
52
53
  indentedAbandon = regexp.MustCompile(`^\s+ABANDON:`)
53
54
  expectSlashRE = regexp.MustCompile(`^/(.*)/([ims]*)$`)
@@ -114,6 +115,8 @@ func ParseLedger(text string) *Document {
114
115
  current.Expect = value
115
116
  case "CWD":
116
117
  current.Cwd = value
118
+ case "INPUTS":
119
+ current.Inputs = value
117
120
  case "EVIDENCE":
118
121
  current.Evidence = value
119
122
  }
@@ -87,9 +87,22 @@ func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, appro
87
87
  results = append(results, GateResult{ID: gate.ID, Detail: "manual gate; evidence is judged, not executed"})
88
88
  continue
89
89
  }
90
- if GateState(gate, doc) == StateMet && !reverify {
91
- results = append(results, GateResult{ID: gate.ID, Passed: true, Detail: "already met"})
92
- continue
90
+ inputs := ""
91
+ if gate.Inputs != "" {
92
+ // An unreadable or unmatched spec yields "", which never matches, so the gate re-executes.
93
+ inputs, _ = InputsDigest(root, gate.Inputs)
94
+ }
95
+ if GateState(gate, doc) == StateMet {
96
+ if gate.Inputs == "" && !reverify {
97
+ results = append(results, GateResult{ID: gate.ID, Passed: true, Detail: "already met"})
98
+ continue
99
+ }
100
+ // A gate that declares INPUTS is reused, under run and reverify alike,
101
+ // exactly while the files it depends on are byte-identical; any change re-executes it.
102
+ if inputs != "" && inputs == recordedInputs(gate) {
103
+ results = append(results, GateResult{ID: gate.ID, Passed: true, Detail: "reused: inputs unchanged"})
104
+ continue
105
+ }
93
106
  }
94
107
  if !Approved(gate.Check, gate.Cwd, approved) {
95
108
  results = append(results, GateResult{ID: gate.ID, Detail: "CHECK/CWD is not an approved test-plan.md preflight row; not executed"})
@@ -111,7 +124,7 @@ func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, appro
111
124
  outcomes[key] = outcome
112
125
  }
113
126
  if outcome.Err == nil && outcome.ExitCode == 0 && expectation.Matches(outcome.Output) {
114
- evidence := FormatEvidence(gate, OutputDigest(outcome.Output), outcome.OutputBytes, time.Now().UTC().Format(time.RFC3339))
127
+ evidence := withInputs(FormatEvidence(gate, OutputDigest(outcome.Output), outcome.OutputBytes, time.Now().UTC().Format(time.RFC3339)), inputs)
115
128
  writeErr := write(gate, evidence, true)
116
129
  results = append(results, GateResult{
117
130
  ID: gate.ID, Ran: true, Passed: true,
@@ -170,7 +183,7 @@ func writeResult(ledgerPath string, ran *Gate, evidence string, checked bool) er
170
183
  if current == nil {
171
184
  return fmt.Errorf("%w: gate %s disappeared", ErrOracleMoved, ran.ID)
172
185
  }
173
- if current.Check != ran.Check || current.Expect != ran.Expect || current.Cwd != ran.Cwd {
186
+ if current.Check != ran.Check || current.Expect != ran.Expect || current.Cwd != ran.Cwd || current.Inputs != ran.Inputs {
174
187
  return fmt.Errorf("%w: gate %s definition edited", ErrOracleMoved, ran.ID)
175
188
  }
176
189
 
@@ -0,0 +1,147 @@
1
+ package lib
2
+
3
+ import (
4
+ "errors"
5
+ "fmt"
6
+ "os"
7
+ "path/filepath"
8
+ "regexp"
9
+ "sort"
10
+ "strings"
11
+
12
+ "github.com/devrites/devrites/internal/devritespaths"
13
+ )
14
+
15
+ // CandidateOmissions lists the project paths the feature's own
16
+ // `WIP(<slug>):` checkpoint commits changed (net, feature base to HEAD) that
17
+ // touched-files.md lists neither present nor deleted. The base is the parent
18
+ // of the oldest such commit, or the empty tree for a root commit. A project
19
+ // outside Git, or with no checkpoint yet, has nothing to compare.
20
+ func CandidateOmissions(root, slug string) ([]string, error) {
21
+ workspace, err := devritespaths.ExistingFeatureDirChecked(root, slug)
22
+ if err != nil {
23
+ return nil, err
24
+ }
25
+ raw, err := readBoundedRegularFile(filepath.Join(workspace, "touched-files.md"), maxCandidateManifestBytes)
26
+ if err != nil {
27
+ return nil, fmt.Errorf("candidate manifest: %w", err)
28
+ }
29
+ rows, err := parseCandidateManifest(raw)
30
+ if err != nil {
31
+ return nil, fmt.Errorf("candidate manifest: %w", err)
32
+ }
33
+ project := projectDir(root)
34
+ if _, err := runGitCommand(project, nil, "rev-parse", "--verify", "--quiet", "HEAD"); err != nil {
35
+ return nil, nil
36
+ }
37
+ log, err := runGitCommand(project, nil, "log", "--fixed-strings", "--grep=WIP("+slug+"):", "--format=%H%x1f%P%x1f%s", "HEAD")
38
+ if err != nil {
39
+ return nil, err
40
+ }
41
+ var commits []string
42
+ base := ""
43
+ for _, line := range strings.Split(strings.TrimSpace(string(log)), "\n") {
44
+ fields := strings.SplitN(line, "\x1f", 3)
45
+ if len(fields) != 3 || !strings.HasPrefix(fields[2], "WIP("+slug+"):") {
46
+ continue
47
+ }
48
+ commits = append(commits, fields[0])
49
+ base = "" // log lists newest first, so the oldest commit's parent wins
50
+ if parents := strings.Fields(fields[1]); len(parents) > 0 {
51
+ base = parents[0]
52
+ }
53
+ }
54
+ if len(commits) == 0 {
55
+ return nil, nil
56
+ }
57
+ if base == "" {
58
+ tree, err := runGitCommandIO(project, nil, []byte{}, nil, "hash-object", "-t", "tree", "--stdin")
59
+ if err != nil {
60
+ return nil, err
61
+ }
62
+ base = strings.TrimSpace(string(tree))
63
+ }
64
+ touched := map[string]bool{}
65
+ for _, commit := range commits {
66
+ names, err := runGitCommand(project, nil, "diff-tree", "-r", "--root", "--no-commit-id", "--name-only", "--no-renames", "--relative", "-z", commit)
67
+ if err != nil {
68
+ return nil, err
69
+ }
70
+ for _, name := range strings.Split(string(names), "\x00") {
71
+ if name != "" {
72
+ touched[name] = true
73
+ }
74
+ }
75
+ }
76
+ net, err := runGitCommand(project, nil, "diff", "--name-only", "--no-renames", "--relative", "-z", base, "HEAD")
77
+ if err != nil {
78
+ return nil, err
79
+ }
80
+ listed := make(map[string]bool, len(rows))
81
+ for _, row := range rows {
82
+ listed[row.path] = true
83
+ }
84
+ var omitted []string
85
+ for _, name := range strings.Split(string(net), "\x00") {
86
+ if name != "" && touched[name] && !listed[name] && !forbiddenCandidateStatePath(name) {
87
+ omitted = append(omitted, name)
88
+ }
89
+ }
90
+ sort.Strings(omitted)
91
+ return omitted, nil
92
+ }
93
+
94
+ // CheckCandidateCoverage fails when touched-files.md omits a path the
95
+ // feature's checkpoints changed.
96
+ func CheckCandidateCoverage(root, slug string) error {
97
+ omitted, err := CandidateOmissions(root, slug)
98
+ if err != nil {
99
+ return err
100
+ }
101
+ if len(omitted) > 0 {
102
+ return omissionError(omitted)
103
+ }
104
+ return nil
105
+ }
106
+
107
+ // omissionError renders a bounded BLOCKED reason for omitted candidate paths.
108
+ func omissionError(omitted []string) error {
109
+ shown := omitted
110
+ if len(shown) > 10 {
111
+ shown = shown[:10]
112
+ }
113
+ more := ""
114
+ if len(omitted) > len(shown) {
115
+ more = ", …"
116
+ }
117
+ return fmt.Errorf("candidate manifest omits %d path(s) the feature's WIP checkpoints changed: %s%s; add each to touched-files.md (present or deleted) before freezing the candidate",
118
+ len(omitted), strings.Join(shown, ", "), more)
119
+ }
120
+
121
+ var diffPathRE = regexp.MustCompile(`[A-Za-z0-9_./@+-]+\.(?:diff|patch)\b`)
122
+
123
+ // lintReviewCandidate gates a Review or Seal reviewer wave: the candidate
124
+ // manifest must cover every path the feature's checkpoints changed, and the
125
+ // packet must name the frozen candidate diff the reviewers judge. A digest
126
+ // alone, or a manifest that drops changed files, voids every reviewer at once.
127
+ func lintReviewCandidate(root, slug, packet string) error {
128
+ if err := CheckCandidateCoverage(root, slug); err != nil {
129
+ return err
130
+ }
131
+ raw, err := readBoundedRegularFile(packet, maxContractBytes)
132
+ if err != nil {
133
+ return fmt.Errorf("reviewer packet %s: %w", packet, err)
134
+ }
135
+ for _, name := range diffPathRE.FindAllString(string(raw), -1) {
136
+ for _, dir := range []string{projectDir(root), featureDir(root, slug), ""} {
137
+ candidate := name
138
+ if dir != "" {
139
+ candidate = filepath.Join(dir, filepath.FromSlash(name))
140
+ }
141
+ if info, err := os.Stat(candidate); err == nil && info.Mode().IsRegular() && info.Size() > 0 {
142
+ return nil
143
+ }
144
+ }
145
+ }
146
+ return errors.New("a Review or Seal reviewer packet must name the frozen candidate diff (an existing non-empty .diff or .patch file); reviewers cannot judge a digest alone")
147
+ }
@@ -223,6 +223,11 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
223
223
  if err := lintReviewerPacket(packet); err != nil {
224
224
  return fail("BLOCKED: %v", err)
225
225
  }
226
+ if strings.EqualFold(phase, "review") || strings.EqualFold(phase, "seal") {
227
+ if err := lintReviewCandidate(root, slug, packet); err != nil {
228
+ return fail("BLOCKED: %v", err)
229
+ }
230
+ }
226
231
  break
227
232
  }
228
233
  }
package/engine/main.go CHANGED
@@ -225,6 +225,10 @@ func cmdCandidate(root string, args []string, stdout, stderr io.Writer) int {
225
225
  fmt.Fprintf(stderr, "candidate: BLOCKED: %v\n", err)
226
226
  return exitBlocked
227
227
  }
228
+ if err := lib.CheckCandidateCoverage(root, args[0]); err != nil {
229
+ fmt.Fprintf(stderr, "candidate: BLOCKED: %v\n", err)
230
+ return exitBlocked
231
+ }
228
232
  fmt.Fprintf(stdout, "candidate-sha256: %s\ncandidate-files: %d\n", digest, files)
229
233
  return exitOK
230
234
  }
@@ -50,7 +50,8 @@ If the candidate diff has no matching surfaces, record
50
50
  (`devrites-engine dispatch <slug> open --phase <p> --wave <w> --role <r>...`;
51
51
  a reviewer, auditor, or test-analyst wave adds `--packet <file>`, the exact packet
52
52
  those agents receive, and `open` refuses one that seeds a gate result, outcome,
53
- count, or "already passed" claim), launch every native agent nonblocking, record each returned host handle
53
+ count, or "already passed" claim; at Review and Seal it also needs the packet to
54
+ name the frozen candidate diff and the manifest to cover every checkpointed path), launch every native agent nonblocking, record each returned host handle
54
55
  (`dispatch ... start --wave <w> --role <r> --handle <id>`), then `seal` —
55
56
  seal fails until every declared role has a distinct handle, which makes a
56
57
  serial launch-then-wait pattern impossible to record. Record each completion
@@ -19,6 +19,7 @@ every later phase requires its presence; proof phases require every gate met.
19
19
  CHECK: <exact command from test-plan.md Build-entry preflight>
20
20
  EXPECT: <substring | /regex/flags>
21
21
  CWD: <repository-relative path; omit for repo root>
22
+ INPUTS: <optional project-relative paths, directories or globs>
22
23
  EVIDENCE: pending
23
24
 
24
25
  - [x] AC-002: <human-judged outcome>
@@ -33,6 +34,10 @@ ABANDON: AC-003 <why the outcome can never be met>
33
34
  - Attributes are indented beneath their gate. Fenced code examples never parse.
34
35
  - `EXPECT` is a substring, or `/pattern/flags` with `i`, `m`, `s` flags.
35
36
  - `CWD` is repository-relative; absolute paths and `..` segments are rejected.
37
+ - `INPUTS` (optional) lists every file the gate's result depends on: sources,
38
+ tests, fixtures, build and lock files, and the gate's own scripts. Each pattern
39
+ must match a file. A gate whose verdict depends on state that no file
40
+ captures (network, wall clock, live host load) takes no `INPUTS`.
36
41
  - `ABANDON:` starts at column 1, names a known gate, and requires a reason.
37
42
  An abandoned gate blocks completion and demands a handoff — it can never
38
43
  read as a pass.
@@ -53,8 +58,8 @@ directory with a bounded timeout; pass requires exit 0 **and** output matching
53
58
  EXPECT. Output is captured but never persisted — only its digest.
54
59
 
55
60
  After Vet READY the runnable set is fixed: converting a runnable gate to manual,
56
- loosening EXPECT, changing CHECK or CWD, or removing a row is Plan repair plus
57
- re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
61
+ loosening EXPECT, changing CHECK or CWD, adding or narrowing INPUTS, or removing
62
+ a row is Plan repair plus re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
58
63
  with `unavailable: <reason>`. Prove attests only gates that were manual at Vet,
59
64
  checked by diffing `gates.md` against the Vet record. **Failing case:** AC-003's
60
65
  runnable gate fails at Prove, the root deletes CHECK/EXPECT and attests "verified
@@ -63,9 +68,13 @@ manually", and `gates status` reads `all-met`.
63
68
  ## Evidence binding
64
69
 
65
70
  A successful run writes `EVIDENCE: automatic-evidence=v1; def=<sha256>; …`
66
- where `def` binds the exact CHECK/EXPECT/CWD the run executed. Editing any of
67
- the three renders prior evidence `stale-unmet` — a stale pass cannot hide
68
- behind an edited oracle. Prose evidence on a runnable gate is also stale:
71
+ where `def` binds the exact CHECK/EXPECT/CWD (and INPUTS, when declared) the run
72
+ executed. Editing any of them renders prior evidence `stale-unmet` — a stale pass
73
+ cannot hide behind an edited oracle. A gate with `INPUTS` also records
74
+ `inputs=<sha256>` of those files' content. `gates run` and `gates reverify`
75
+ both reuse such a gate while the files are byte-identical, and re-execute it
76
+ when any of them changes. A gate without `INPUTS` keeps the plain rule:
77
+ `run` skips it once met, and `reverify` re-executes it. Prose evidence on a runnable gate is also stale:
69
78
  only automatic evidence bound to the current definition counts.
70
79
 
71
80
  Manual gates are met when checked with non-placeholder human evidence;
@@ -85,8 +94,8 @@ self-attests.
85
94
  ```text
86
95
  devrites-engine gates scaffold <slug> create gates.md from spec.md AC ids
87
96
  devrites-engine gates status <slug> reduce without executing; all-met/handoff/not-met/malformed
88
- devrites-engine gates run <slug> execute unmet runnable gates, write bound evidence
89
- devrites-engine gates reverify <slug> re-execute every runnable gate; demote stale passes
97
+ devrites-engine gates run <slug> execute unmet runnable gates (and INPUTS gates whose files changed)
98
+ devrites-engine gates reverify <slug> re-execute every runnable gate except INPUTS gates with unchanged files
90
99
  devrites-engine gates lint <slug> audit oracle quality without executing
91
100
  devrites-engine gates attest <slug> <id> <note> record human evidence on a manual gate
92
101
  devrites-engine gates abandon <slug> <id> <why> record a terminal ABANDON handoff
@@ -89,7 +89,12 @@ Pull these via `Read` when the diff demands them:
89
89
  what the spec asked but breaks project conventions (Spec pass, Code-review fail).
90
90
  Separate contexts prevent one axis from masking the other. This is the initial
91
91
  full pass; a correction uses step 7's bounded recheck:
92
- - Freeze that closed digest and give the same digest to the **two** axis reviewers (three when
92
+ - Freeze that closed digest: write the candidate diff (feature base to HEAD, plus any
93
+ uncommitted candidate change) to `packets/<wave>.diff` and name that file in the
94
+ packet. `dispatch open` refuses a Review or Seal reviewer wave whose packet names
95
+ no existing diff, or whose manifest omits a path the feature's `WIP(<slug>):`
96
+ checkpoints changed.
97
+ Give the same digest and diff to the **two** axis reviewers (three when
93
98
  [engineering lanes](../devrites-lib/reference/parallel-dispatch.md) split the code axis) in
94
99
  parallel, each with its own narrow brief and no
95
100
  cross-pollination — and evaluate the frontend (step 4), security (step 5),
@@ -50,7 +50,8 @@ If the candidate diff has no matching surfaces, record
50
50
  (`devrites-engine dispatch <slug> open --phase <p> --wave <w> --role <r>...`;
51
51
  a reviewer, auditor, or test-analyst wave adds `--packet <file>`, the exact packet
52
52
  those agents receive, and `open` refuses one that seeds a gate result, outcome,
53
- count, or "already passed" claim), launch every native agent nonblocking, record each returned host handle
53
+ count, or "already passed" claim; at Review and Seal it also needs the packet to
54
+ name the frozen candidate diff and the manifest to cover every checkpointed path), launch every native agent nonblocking, record each returned host handle
54
55
  (`dispatch ... start --wave <w> --role <r> --handle <id>`), then `seal` —
55
56
  seal fails until every declared role has a distinct handle, which makes a
56
57
  serial launch-then-wait pattern impossible to record. Record each completion
@@ -19,6 +19,7 @@ every later phase requires its presence; proof phases require every gate met.
19
19
  CHECK: <exact command from test-plan.md Build-entry preflight>
20
20
  EXPECT: <substring | /regex/flags>
21
21
  CWD: <repository-relative path; omit for repo root>
22
+ INPUTS: <optional project-relative paths, directories or globs>
22
23
  EVIDENCE: pending
23
24
 
24
25
  - [x] AC-002: <human-judged outcome>
@@ -33,6 +34,10 @@ ABANDON: AC-003 <why the outcome can never be met>
33
34
  - Attributes are indented beneath their gate. Fenced code examples never parse.
34
35
  - `EXPECT` is a substring, or `/pattern/flags` with `i`, `m`, `s` flags.
35
36
  - `CWD` is repository-relative; absolute paths and `..` segments are rejected.
37
+ - `INPUTS` (optional) lists every file the gate's result depends on: sources,
38
+ tests, fixtures, build and lock files, and the gate's own scripts. Each pattern
39
+ must match a file. A gate whose verdict depends on state that no file
40
+ captures (network, wall clock, live host load) takes no `INPUTS`.
36
41
  - `ABANDON:` starts at column 1, names a known gate, and requires a reason.
37
42
  An abandoned gate blocks completion and demands a handoff — it can never
38
43
  read as a pass.
@@ -53,8 +58,8 @@ directory with a bounded timeout; pass requires exit 0 **and** output matching
53
58
  EXPECT. Output is captured but never persisted — only its digest.
54
59
 
55
60
  After Vet READY the runnable set is fixed: converting a runnable gate to manual,
56
- loosening EXPECT, changing CHECK or CWD, or removing a row is Plan repair plus
57
- re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
61
+ loosening EXPECT, changing CHECK or CWD, adding or narrowing INPUTS, or removing
62
+ a row is Plan repair plus re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
58
63
  with `unavailable: <reason>`. Prove attests only gates that were manual at Vet,
59
64
  checked by diffing `gates.md` against the Vet record. **Failing case:** AC-003's
60
65
  runnable gate fails at Prove, the root deletes CHECK/EXPECT and attests "verified
@@ -63,9 +68,13 @@ manually", and `gates status` reads `all-met`.
63
68
  ## Evidence binding
64
69
 
65
70
  A successful run writes `EVIDENCE: automatic-evidence=v1; def=<sha256>; …`
66
- where `def` binds the exact CHECK/EXPECT/CWD the run executed. Editing any of
67
- the three renders prior evidence `stale-unmet` — a stale pass cannot hide
68
- behind an edited oracle. Prose evidence on a runnable gate is also stale:
71
+ where `def` binds the exact CHECK/EXPECT/CWD (and INPUTS, when declared) the run
72
+ executed. Editing any of them renders prior evidence `stale-unmet` — a stale pass
73
+ cannot hide behind an edited oracle. A gate with `INPUTS` also records
74
+ `inputs=<sha256>` of those files' content. `gates run` and `gates reverify`
75
+ both reuse such a gate while the files are byte-identical, and re-execute it
76
+ when any of them changes. A gate without `INPUTS` keeps the plain rule:
77
+ `run` skips it once met, and `reverify` re-executes it. Prose evidence on a runnable gate is also stale:
69
78
  only automatic evidence bound to the current definition counts.
70
79
 
71
80
  Manual gates are met when checked with non-placeholder human evidence;
@@ -85,8 +94,8 @@ self-attests.
85
94
  ```text
86
95
  devrites-engine gates scaffold <slug> create gates.md from spec.md AC ids
87
96
  devrites-engine gates status <slug> reduce without executing; all-met/handoff/not-met/malformed
88
- devrites-engine gates run <slug> execute unmet runnable gates, write bound evidence
89
- devrites-engine gates reverify <slug> re-execute every runnable gate; demote stale passes
97
+ devrites-engine gates run <slug> execute unmet runnable gates (and INPUTS gates whose files changed)
98
+ devrites-engine gates reverify <slug> re-execute every runnable gate except INPUTS gates with unchanged files
90
99
  devrites-engine gates lint <slug> audit oracle quality without executing
91
100
  devrites-engine gates attest <slug> <id> <note> record human evidence on a manual gate
92
101
  devrites-engine gates abandon <slug> <id> <why> record a terminal ABANDON handoff
@@ -89,7 +89,12 @@ Pull these via `Read` when the diff demands them:
89
89
  what the spec asked but breaks project conventions (Spec pass, Code-review fail).
90
90
  Separate contexts prevent one axis from masking the other. This is the initial
91
91
  full pass; a correction uses step 7's bounded recheck:
92
- - Freeze that closed digest and give the same digest to the **two** axis reviewers (three when
92
+ - Freeze that closed digest: write the candidate diff (feature base to HEAD, plus any
93
+ uncommitted candidate change) to `packets/<wave>.diff` and name that file in the
94
+ packet. `dispatch open` refuses a Review or Seal reviewer wave whose packet names
95
+ no existing diff, or whose manifest omits a path the feature's `WIP(<slug>):`
96
+ checkpoints changed.
97
+ Give the same digest and diff to the **two** axis reviewers (three when
93
98
  [engineering lanes](../devrites-lib/reference/parallel-dispatch.md) split the code axis) in
94
99
  parallel, each with its own narrow brief and no
95
100
  cross-pollination — and evaluate the frontend (step 4), security (step 5),
@@ -50,7 +50,8 @@ If the candidate diff has no matching surfaces, record
50
50
  (`devrites-engine dispatch <slug> open --phase <p> --wave <w> --role <r>...`;
51
51
  a reviewer, auditor, or test-analyst wave adds `--packet <file>`, the exact packet
52
52
  those agents receive, and `open` refuses one that seeds a gate result, outcome,
53
- count, or "already passed" claim), launch every native agent nonblocking, record each returned host handle
53
+ count, or "already passed" claim; at Review and Seal it also needs the packet to
54
+ name the frozen candidate diff and the manifest to cover every checkpointed path), launch every native agent nonblocking, record each returned host handle
54
55
  (`dispatch ... start --wave <w> --role <r> --handle <id>`), then `seal` —
55
56
  seal fails until every declared role has a distinct handle, which makes a
56
57
  serial launch-then-wait pattern impossible to record. Record each completion
@@ -19,6 +19,7 @@ every later phase requires its presence; proof phases require every gate met.
19
19
  CHECK: <exact command from test-plan.md Build-entry preflight>
20
20
  EXPECT: <substring | /regex/flags>
21
21
  CWD: <repository-relative path; omit for repo root>
22
+ INPUTS: <optional project-relative paths, directories or globs>
22
23
  EVIDENCE: pending
23
24
 
24
25
  - [x] AC-002: <human-judged outcome>
@@ -33,6 +34,10 @@ ABANDON: AC-003 <why the outcome can never be met>
33
34
  - Attributes are indented beneath their gate. Fenced code examples never parse.
34
35
  - `EXPECT` is a substring, or `/pattern/flags` with `i`, `m`, `s` flags.
35
36
  - `CWD` is repository-relative; absolute paths and `..` segments are rejected.
37
+ - `INPUTS` (optional) lists every file the gate's result depends on: sources,
38
+ tests, fixtures, build and lock files, and the gate's own scripts. Each pattern
39
+ must match a file. A gate whose verdict depends on state that no file
40
+ captures (network, wall clock, live host load) takes no `INPUTS`.
36
41
  - `ABANDON:` starts at column 1, names a known gate, and requires a reason.
37
42
  An abandoned gate blocks completion and demands a handoff — it can never
38
43
  read as a pass.
@@ -53,8 +58,8 @@ directory with a bounded timeout; pass requires exit 0 **and** output matching
53
58
  EXPECT. Output is captured but never persisted — only its digest.
54
59
 
55
60
  After Vet READY the runnable set is fixed: converting a runnable gate to manual,
56
- loosening EXPECT, changing CHECK or CWD, or removing a row is Plan repair plus
57
- re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
61
+ loosening EXPECT, changing CHECK or CWD, adding or narrowing INPUTS, or removing
62
+ a row is Plan repair plus re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
58
63
  with `unavailable: <reason>`. Prove attests only gates that were manual at Vet,
59
64
  checked by diffing `gates.md` against the Vet record. **Failing case:** AC-003's
60
65
  runnable gate fails at Prove, the root deletes CHECK/EXPECT and attests "verified
@@ -63,9 +68,13 @@ manually", and `gates status` reads `all-met`.
63
68
  ## Evidence binding
64
69
 
65
70
  A successful run writes `EVIDENCE: automatic-evidence=v1; def=<sha256>; …`
66
- where `def` binds the exact CHECK/EXPECT/CWD the run executed. Editing any of
67
- the three renders prior evidence `stale-unmet` — a stale pass cannot hide
68
- behind an edited oracle. Prose evidence on a runnable gate is also stale:
71
+ where `def` binds the exact CHECK/EXPECT/CWD (and INPUTS, when declared) the run
72
+ executed. Editing any of them renders prior evidence `stale-unmet` — a stale pass
73
+ cannot hide behind an edited oracle. A gate with `INPUTS` also records
74
+ `inputs=<sha256>` of those files' content. `gates run` and `gates reverify`
75
+ both reuse such a gate while the files are byte-identical, and re-execute it
76
+ when any of them changes. A gate without `INPUTS` keeps the plain rule:
77
+ `run` skips it once met, and `reverify` re-executes it. Prose evidence on a runnable gate is also stale:
69
78
  only automatic evidence bound to the current definition counts.
70
79
 
71
80
  Manual gates are met when checked with non-placeholder human evidence;
@@ -85,8 +94,8 @@ self-attests.
85
94
  ```text
86
95
  devrites-engine gates scaffold <slug> create gates.md from spec.md AC ids
87
96
  devrites-engine gates status <slug> reduce without executing; all-met/handoff/not-met/malformed
88
- devrites-engine gates run <slug> execute unmet runnable gates, write bound evidence
89
- devrites-engine gates reverify <slug> re-execute every runnable gate; demote stale passes
97
+ devrites-engine gates run <slug> execute unmet runnable gates (and INPUTS gates whose files changed)
98
+ devrites-engine gates reverify <slug> re-execute every runnable gate except INPUTS gates with unchanged files
90
99
  devrites-engine gates lint <slug> audit oracle quality without executing
91
100
  devrites-engine gates attest <slug> <id> <note> record human evidence on a manual gate
92
101
  devrites-engine gates abandon <slug> <id> <why> record a terminal ABANDON handoff
@@ -89,7 +89,12 @@ Pull these via `Read` when the diff demands them:
89
89
  what the spec asked but breaks project conventions (Spec pass, Code-review fail).
90
90
  Separate contexts prevent one axis from masking the other. This is the initial
91
91
  full pass; a correction uses step 7's bounded recheck:
92
- - Freeze that closed digest and give the same digest to the **two** axis reviewers (three when
92
+ - Freeze that closed digest: write the candidate diff (feature base to HEAD, plus any
93
+ uncommitted candidate change) to `packets/<wave>.diff` and name that file in the
94
+ packet. `dispatch open` refuses a Review or Seal reviewer wave whose packet names
95
+ no existing diff, or whose manifest omits a path the feature's `WIP(<slug>):`
96
+ checkpoints changed.
97
+ Give the same digest and diff to the **two** axis reviewers (three when
93
98
  [engineering lanes](../devrites-lib/reference/parallel-dispatch.md) split the code axis) in
94
99
  parallel, each with its own narrow brief and no
95
100
  cross-pollination — and evaluate the frontend (step 4), security (step 5),
@@ -50,7 +50,8 @@ If the candidate diff has no matching surfaces, record
50
50
  (`devrites-engine dispatch <slug> open --phase <p> --wave <w> --role <r>...`;
51
51
  a reviewer, auditor, or test-analyst wave adds `--packet <file>`, the exact packet
52
52
  those agents receive, and `open` refuses one that seeds a gate result, outcome,
53
- count, or "already passed" claim), launch every native agent nonblocking, record each returned host handle
53
+ count, or "already passed" claim; at Review and Seal it also needs the packet to
54
+ name the frozen candidate diff and the manifest to cover every checkpointed path), launch every native agent nonblocking, record each returned host handle
54
55
  (`dispatch ... start --wave <w> --role <r> --handle <id>`), then `seal` —
55
56
  seal fails until every declared role has a distinct handle, which makes a
56
57
  serial launch-then-wait pattern impossible to record. Record each completion
@@ -19,6 +19,7 @@ every later phase requires its presence; proof phases require every gate met.
19
19
  CHECK: <exact command from test-plan.md Build-entry preflight>
20
20
  EXPECT: <substring | /regex/flags>
21
21
  CWD: <repository-relative path; omit for repo root>
22
+ INPUTS: <optional project-relative paths, directories or globs>
22
23
  EVIDENCE: pending
23
24
 
24
25
  - [x] AC-002: <human-judged outcome>
@@ -33,6 +34,10 @@ ABANDON: AC-003 <why the outcome can never be met>
33
34
  - Attributes are indented beneath their gate. Fenced code examples never parse.
34
35
  - `EXPECT` is a substring, or `/pattern/flags` with `i`, `m`, `s` flags.
35
36
  - `CWD` is repository-relative; absolute paths and `..` segments are rejected.
37
+ - `INPUTS` (optional) lists every file the gate's result depends on: sources,
38
+ tests, fixtures, build and lock files, and the gate's own scripts. Each pattern
39
+ must match a file. A gate whose verdict depends on state that no file
40
+ captures (network, wall clock, live host load) takes no `INPUTS`.
36
41
  - `ABANDON:` starts at column 1, names a known gate, and requires a reason.
37
42
  An abandoned gate blocks completion and demands a handoff — it can never
38
43
  read as a pass.
@@ -53,8 +58,8 @@ directory with a bounded timeout; pass requires exit 0 **and** output matching
53
58
  EXPECT. Output is captured but never persisted — only its digest.
54
59
 
55
60
  After Vet READY the runnable set is fixed: converting a runnable gate to manual,
56
- loosening EXPECT, changing CHECK or CWD, or removing a row is Plan repair plus
57
- re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
61
+ loosening EXPECT, changing CHECK or CWD, adding or narrowing INPUTS, or removing
62
+ a row is Plan repair plus re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
58
63
  with `unavailable: <reason>`. Prove attests only gates that were manual at Vet,
59
64
  checked by diffing `gates.md` against the Vet record. **Failing case:** AC-003's
60
65
  runnable gate fails at Prove, the root deletes CHECK/EXPECT and attests "verified
@@ -63,9 +68,13 @@ manually", and `gates status` reads `all-met`.
63
68
  ## Evidence binding
64
69
 
65
70
  A successful run writes `EVIDENCE: automatic-evidence=v1; def=<sha256>; …`
66
- where `def` binds the exact CHECK/EXPECT/CWD the run executed. Editing any of
67
- the three renders prior evidence `stale-unmet` — a stale pass cannot hide
68
- behind an edited oracle. Prose evidence on a runnable gate is also stale:
71
+ where `def` binds the exact CHECK/EXPECT/CWD (and INPUTS, when declared) the run
72
+ executed. Editing any of them renders prior evidence `stale-unmet` — a stale pass
73
+ cannot hide behind an edited oracle. A gate with `INPUTS` also records
74
+ `inputs=<sha256>` of those files' content. `gates run` and `gates reverify`
75
+ both reuse such a gate while the files are byte-identical, and re-execute it
76
+ when any of them changes. A gate without `INPUTS` keeps the plain rule:
77
+ `run` skips it once met, and `reverify` re-executes it. Prose evidence on a runnable gate is also stale:
69
78
  only automatic evidence bound to the current definition counts.
70
79
 
71
80
  Manual gates are met when checked with non-placeholder human evidence;
@@ -85,8 +94,8 @@ self-attests.
85
94
  ```text
86
95
  devrites-engine gates scaffold <slug> create gates.md from spec.md AC ids
87
96
  devrites-engine gates status <slug> reduce without executing; all-met/handoff/not-met/malformed
88
- devrites-engine gates run <slug> execute unmet runnable gates, write bound evidence
89
- devrites-engine gates reverify <slug> re-execute every runnable gate; demote stale passes
97
+ devrites-engine gates run <slug> execute unmet runnable gates (and INPUTS gates whose files changed)
98
+ devrites-engine gates reverify <slug> re-execute every runnable gate except INPUTS gates with unchanged files
90
99
  devrites-engine gates lint <slug> audit oracle quality without executing
91
100
  devrites-engine gates attest <slug> <id> <note> record human evidence on a manual gate
92
101
  devrites-engine gates abandon <slug> <id> <why> record a terminal ABANDON handoff
@@ -88,7 +88,12 @@ Pull these via `Read` when the diff demands them:
88
88
  what the spec asked but breaks project conventions (Spec pass, Code-review fail).
89
89
  Separate contexts prevent one axis from masking the other. This is the initial
90
90
  full pass; a correction uses step 7's bounded recheck:
91
- - Freeze that closed digest and give the same digest to the **two** axis reviewers (three when
91
+ - Freeze that closed digest: write the candidate diff (feature base to HEAD, plus any
92
+ uncommitted candidate change) to `packets/<wave>.diff` and name that file in the
93
+ packet. `dispatch open` refuses a Review or Seal reviewer wave whose packet names
94
+ no existing diff, or whose manifest omits a path the feature's `WIP(<slug>):`
95
+ checkpoints changed.
96
+ Give the same digest and diff to the **two** axis reviewers (three when
92
97
  [engineering lanes](../devrites-lib/reference/parallel-dispatch.md) split the code axis) in
93
98
  parallel, each with its own narrow brief and no
94
99
  cross-pollination — and evaluate the frontend (step 4), security (step 5),
@@ -50,7 +50,8 @@ If the candidate diff has no matching surfaces, record
50
50
  (`devrites-engine dispatch <slug> open --phase <p> --wave <w> --role <r>...`;
51
51
  a reviewer, auditor, or test-analyst wave adds `--packet <file>`, the exact packet
52
52
  those agents receive, and `open` refuses one that seeds a gate result, outcome,
53
- count, or "already passed" claim), launch every native agent nonblocking, record each returned host handle
53
+ count, or "already passed" claim; at Review and Seal it also needs the packet to
54
+ name the frozen candidate diff and the manifest to cover every checkpointed path), launch every native agent nonblocking, record each returned host handle
54
55
  (`dispatch ... start --wave <w> --role <r> --handle <id>`), then `seal` —
55
56
  seal fails until every declared role has a distinct handle, which makes a
56
57
  serial launch-then-wait pattern impossible to record. Record each completion
@@ -19,6 +19,7 @@ every later phase requires its presence; proof phases require every gate met.
19
19
  CHECK: <exact command from test-plan.md Build-entry preflight>
20
20
  EXPECT: <substring | /regex/flags>
21
21
  CWD: <repository-relative path; omit for repo root>
22
+ INPUTS: <optional project-relative paths, directories or globs>
22
23
  EVIDENCE: pending
23
24
 
24
25
  - [x] AC-002: <human-judged outcome>
@@ -33,6 +34,10 @@ ABANDON: AC-003 <why the outcome can never be met>
33
34
  - Attributes are indented beneath their gate. Fenced code examples never parse.
34
35
  - `EXPECT` is a substring, or `/pattern/flags` with `i`, `m`, `s` flags.
35
36
  - `CWD` is repository-relative; absolute paths and `..` segments are rejected.
37
+ - `INPUTS` (optional) lists every file the gate's result depends on: sources,
38
+ tests, fixtures, build and lock files, and the gate's own scripts. Each pattern
39
+ must match a file. A gate whose verdict depends on state that no file
40
+ captures (network, wall clock, live host load) takes no `INPUTS`.
36
41
  - `ABANDON:` starts at column 1, names a known gate, and requires a reason.
37
42
  An abandoned gate blocks completion and demands a handoff — it can never
38
43
  read as a pass.
@@ -53,8 +58,8 @@ directory with a bounded timeout; pass requires exit 0 **and** output matching
53
58
  EXPECT. Output is captured but never persisted — only its digest.
54
59
 
55
60
  After Vet READY the runnable set is fixed: converting a runnable gate to manual,
56
- loosening EXPECT, changing CHECK or CWD, or removing a row is Plan repair plus
57
- re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
61
+ loosening EXPECT, changing CHECK or CWD, adding or narrowing INPUTS, or removing
62
+ a row is Plan repair plus re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
58
63
  with `unavailable: <reason>`. Prove attests only gates that were manual at Vet,
59
64
  checked by diffing `gates.md` against the Vet record. **Failing case:** AC-003's
60
65
  runnable gate fails at Prove, the root deletes CHECK/EXPECT and attests "verified
@@ -63,9 +68,13 @@ manually", and `gates status` reads `all-met`.
63
68
  ## Evidence binding
64
69
 
65
70
  A successful run writes `EVIDENCE: automatic-evidence=v1; def=<sha256>; …`
66
- where `def` binds the exact CHECK/EXPECT/CWD the run executed. Editing any of
67
- the three renders prior evidence `stale-unmet` — a stale pass cannot hide
68
- behind an edited oracle. Prose evidence on a runnable gate is also stale:
71
+ where `def` binds the exact CHECK/EXPECT/CWD (and INPUTS, when declared) the run
72
+ executed. Editing any of them renders prior evidence `stale-unmet` — a stale pass
73
+ cannot hide behind an edited oracle. A gate with `INPUTS` also records
74
+ `inputs=<sha256>` of those files' content. `gates run` and `gates reverify`
75
+ both reuse such a gate while the files are byte-identical, and re-execute it
76
+ when any of them changes. A gate without `INPUTS` keeps the plain rule:
77
+ `run` skips it once met, and `reverify` re-executes it. Prose evidence on a runnable gate is also stale:
69
78
  only automatic evidence bound to the current definition counts.
70
79
 
71
80
  Manual gates are met when checked with non-placeholder human evidence;
@@ -85,8 +94,8 @@ self-attests.
85
94
  ```text
86
95
  devrites-engine gates scaffold <slug> create gates.md from spec.md AC ids
87
96
  devrites-engine gates status <slug> reduce without executing; all-met/handoff/not-met/malformed
88
- devrites-engine gates run <slug> execute unmet runnable gates, write bound evidence
89
- devrites-engine gates reverify <slug> re-execute every runnable gate; demote stale passes
97
+ devrites-engine gates run <slug> execute unmet runnable gates (and INPUTS gates whose files changed)
98
+ devrites-engine gates reverify <slug> re-execute every runnable gate except INPUTS gates with unchanged files
90
99
  devrites-engine gates lint <slug> audit oracle quality without executing
91
100
  devrites-engine gates attest <slug> <id> <note> record human evidence on a manual gate
92
101
  devrites-engine gates abandon <slug> <id> <why> record a terminal ABANDON handoff
@@ -89,7 +89,12 @@ Pull these via `Read` when the diff demands them:
89
89
  what the spec asked but breaks project conventions (Spec pass, Code-review fail).
90
90
  Separate contexts prevent one axis from masking the other. This is the initial
91
91
  full pass; a correction uses step 7's bounded recheck:
92
- - Freeze that closed digest and give the same digest to the **two** axis reviewers (three when
92
+ - Freeze that closed digest: write the candidate diff (feature base to HEAD, plus any
93
+ uncommitted candidate change) to `packets/<wave>.diff` and name that file in the
94
+ packet. `dispatch open` refuses a Review or Seal reviewer wave whose packet names
95
+ no existing diff, or whose manifest omits a path the feature's `WIP(<slug>):`
96
+ checkpoints changed.
97
+ Give the same digest and diff to the **two** axis reviewers (three when
93
98
  [engineering lanes](skill://devrites-lib/reference/parallel-dispatch.md) split the code axis) in
94
99
  parallel, each with its own narrow brief and no
95
100
  cross-pollination — and evaluate the frontend (step 4), security (step 5),
@@ -50,7 +50,8 @@ If the candidate diff has no matching surfaces, record
50
50
  (`devrites-engine dispatch <slug> open --phase <p> --wave <w> --role <r>...`;
51
51
  a reviewer, auditor, or test-analyst wave adds `--packet <file>`, the exact packet
52
52
  those agents receive, and `open` refuses one that seeds a gate result, outcome,
53
- count, or "already passed" claim), launch every native agent nonblocking, record each returned host handle
53
+ count, or "already passed" claim; at Review and Seal it also needs the packet to
54
+ name the frozen candidate diff and the manifest to cover every checkpointed path), launch every native agent nonblocking, record each returned host handle
54
55
  (`dispatch ... start --wave <w> --role <r> --handle <id>`), then `seal` —
55
56
  seal fails until every declared role has a distinct handle, which makes a
56
57
  serial launch-then-wait pattern impossible to record. Record each completion
@@ -19,6 +19,7 @@ every later phase requires its presence; proof phases require every gate met.
19
19
  CHECK: <exact command from test-plan.md Build-entry preflight>
20
20
  EXPECT: <substring | /regex/flags>
21
21
  CWD: <repository-relative path; omit for repo root>
22
+ INPUTS: <optional project-relative paths, directories or globs>
22
23
  EVIDENCE: pending
23
24
 
24
25
  - [x] AC-002: <human-judged outcome>
@@ -33,6 +34,10 @@ ABANDON: AC-003 <why the outcome can never be met>
33
34
  - Attributes are indented beneath their gate. Fenced code examples never parse.
34
35
  - `EXPECT` is a substring, or `/pattern/flags` with `i`, `m`, `s` flags.
35
36
  - `CWD` is repository-relative; absolute paths and `..` segments are rejected.
37
+ - `INPUTS` (optional) lists every file the gate's result depends on: sources,
38
+ tests, fixtures, build and lock files, and the gate's own scripts. Each pattern
39
+ must match a file. A gate whose verdict depends on state that no file
40
+ captures (network, wall clock, live host load) takes no `INPUTS`.
36
41
  - `ABANDON:` starts at column 1, names a known gate, and requires a reason.
37
42
  An abandoned gate blocks completion and demands a handoff — it can never
38
43
  read as a pass.
@@ -53,8 +58,8 @@ directory with a bounded timeout; pass requires exit 0 **and** output matching
53
58
  EXPECT. Output is captured but never persisted — only its digest.
54
59
 
55
60
  After Vet READY the runnable set is fixed: converting a runnable gate to manual,
56
- loosening EXPECT, changing CHECK or CWD, or removing a row is Plan repair plus
57
- re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
61
+ loosening EXPECT, changing CHECK or CWD, adding or narrowing INPUTS, or removing
62
+ a row is Plan repair plus re-Vet, never a Prove or Seal edit. A runnable gate that cannot execute stays unmet
58
63
  with `unavailable: <reason>`. Prove attests only gates that were manual at Vet,
59
64
  checked by diffing `gates.md` against the Vet record. **Failing case:** AC-003's
60
65
  runnable gate fails at Prove, the root deletes CHECK/EXPECT and attests "verified
@@ -63,9 +68,13 @@ manually", and `gates status` reads `all-met`.
63
68
  ## Evidence binding
64
69
 
65
70
  A successful run writes `EVIDENCE: automatic-evidence=v1; def=<sha256>; …`
66
- where `def` binds the exact CHECK/EXPECT/CWD the run executed. Editing any of
67
- the three renders prior evidence `stale-unmet` — a stale pass cannot hide
68
- behind an edited oracle. Prose evidence on a runnable gate is also stale:
71
+ where `def` binds the exact CHECK/EXPECT/CWD (and INPUTS, when declared) the run
72
+ executed. Editing any of them renders prior evidence `stale-unmet` — a stale pass
73
+ cannot hide behind an edited oracle. A gate with `INPUTS` also records
74
+ `inputs=<sha256>` of those files' content. `gates run` and `gates reverify`
75
+ both reuse such a gate while the files are byte-identical, and re-execute it
76
+ when any of them changes. A gate without `INPUTS` keeps the plain rule:
77
+ `run` skips it once met, and `reverify` re-executes it. Prose evidence on a runnable gate is also stale:
69
78
  only automatic evidence bound to the current definition counts.
70
79
 
71
80
  Manual gates are met when checked with non-placeholder human evidence;
@@ -85,8 +94,8 @@ self-attests.
85
94
  ```text
86
95
  devrites-engine gates scaffold <slug> create gates.md from spec.md AC ids
87
96
  devrites-engine gates status <slug> reduce without executing; all-met/handoff/not-met/malformed
88
- devrites-engine gates run <slug> execute unmet runnable gates, write bound evidence
89
- devrites-engine gates reverify <slug> re-execute every runnable gate; demote stale passes
97
+ devrites-engine gates run <slug> execute unmet runnable gates (and INPUTS gates whose files changed)
98
+ devrites-engine gates reverify <slug> re-execute every runnable gate except INPUTS gates with unchanged files
90
99
  devrites-engine gates lint <slug> audit oracle quality without executing
91
100
  devrites-engine gates attest <slug> <id> <note> record human evidence on a manual gate
92
101
  devrites-engine gates abandon <slug> <id> <why> record a terminal ABANDON handoff
@@ -89,7 +89,12 @@ Pull these via `Read` when the diff demands them:
89
89
  what the spec asked but breaks project conventions (Spec pass, Code-review fail).
90
90
  Separate contexts prevent one axis from masking the other. This is the initial
91
91
  full pass; a correction uses step 7's bounded recheck:
92
- - Freeze that closed digest and give the same digest to the **two** axis reviewers (three when
92
+ - Freeze that closed digest: write the candidate diff (feature base to HEAD, plus any
93
+ uncommitted candidate change) to `packets/<wave>.diff` and name that file in the
94
+ packet. `dispatch open` refuses a Review or Seal reviewer wave whose packet names
95
+ no existing diff, or whose manifest omits a path the feature's `WIP(<slug>):`
96
+ checkpoints changed.
97
+ Give the same digest and diff to the **two** axis reviewers (three when
93
98
  [engineering lanes](../devrites-lib/reference/parallel-dispatch.md) split the code axis) in
94
99
  parallel, each with its own narrow brief and no
95
100
  cross-pollination — and evaluate the frontend (step 4), security (step 5),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devrites",
3
- "version": "6.7.4",
3
+ "version": "6.8.0",
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",