devrites 5.12.0 → 5.13.1

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 (138) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -3
  3. package/docs/architecture.md +2 -2
  4. package/docs/command-map.md +3 -1
  5. package/docs/engine/commands.md +21 -0
  6. package/docs/flow.md +2 -1
  7. package/docs/skills.md +13 -4
  8. package/engine/help.go +4 -1
  9. package/engine/help_test.go +2 -0
  10. package/engine/internal/install/binary.go +4 -0
  11. package/engine/internal/install/install_test.go +22 -0
  12. package/engine/internal/overhaul/admit/admit_test.go +170 -0
  13. package/engine/internal/overhaul/admit/run.go +416 -0
  14. package/engine/internal/overhaul/bench/run.go +248 -0
  15. package/engine/internal/overhaul/bench/run_test.go +139 -0
  16. package/engine/internal/overhaul/overhaul.go +64 -0
  17. package/engine/internal/overhaul/ovio/ovio.go +158 -0
  18. package/engine/internal/overhaul/records/check.go +834 -0
  19. package/engine/internal/overhaul/records/records_test.go +577 -0
  20. package/engine/internal/overhaul/records/run.go +392 -0
  21. package/engine/internal/overhaul/render/run.go +776 -0
  22. package/engine/internal/overhaul/render/run_test.go +293 -0
  23. package/engine/internal/overhaul/score/run.go +600 -0
  24. package/engine/internal/overhaul/score/run_test.go +405 -0
  25. package/engine/internal/overhaul/snapshot/run.go +563 -0
  26. package/engine/internal/overhaul/snapshot/run_test.go +360 -0
  27. package/engine/main.go +4 -0
  28. package/engine/root_routing_test.go +2 -1
  29. package/pack/.claude/skills/overhaul/SKILL.md +201 -0
  30. package/pack/.claude/skills/overhaul/references/audit-domains.md +263 -0
  31. package/pack/.claude/skills/overhaul/references/lifecycle.md +308 -0
  32. package/pack/.claude/skills/overhaul/references/orchestration.md +264 -0
  33. package/pack/.claude/skills/overhaul/references/records.md +137 -0
  34. package/pack/.claude/skills/overhaul/references/repair-and-measurement.md +207 -0
  35. package/pack/.claude/skills/overhaul/references/review-and-report.md +137 -0
  36. package/pack/.claude/skills/overhaul/references/scope-and-safety.md +191 -0
  37. package/pack/.claude/skills/overhaul/references/scoring.md +222 -0
  38. package/pack/.claude/skills/overhaul/references/stack-profiles.md +264 -0
  39. package/pack/.claude/skills/overhaul/roles/backend-engineer.md +61 -0
  40. package/pack/.claude/skills/overhaul/roles/boundary.md +56 -0
  41. package/pack/.claude/skills/overhaul/roles/craft.md +77 -0
  42. package/pack/.claude/skills/overhaul/roles/frontend-engineer.md +63 -0
  43. package/pack/.claude/skills/overhaul/roles/implementer.md +59 -0
  44. package/pack/.claude/skills/overhaul/roles/recon.md +60 -0
  45. package/pack/.claude/skills/overhaul/roles/specialist.md +56 -0
  46. package/pack/.claude/skills/overhaul/roles/verifier.md +60 -0
  47. package/pack/generated/claude/skills/overhaul/SKILL.md +201 -0
  48. package/pack/generated/claude/skills/overhaul/references/audit-domains.md +263 -0
  49. package/pack/generated/claude/skills/overhaul/references/lifecycle.md +308 -0
  50. package/pack/generated/claude/skills/overhaul/references/orchestration.md +264 -0
  51. package/pack/generated/claude/skills/overhaul/references/records.md +137 -0
  52. package/pack/generated/claude/skills/overhaul/references/repair-and-measurement.md +207 -0
  53. package/pack/generated/claude/skills/overhaul/references/review-and-report.md +137 -0
  54. package/pack/generated/claude/skills/overhaul/references/scope-and-safety.md +191 -0
  55. package/pack/generated/claude/skills/overhaul/references/scoring.md +222 -0
  56. package/pack/generated/claude/skills/overhaul/references/stack-profiles.md +264 -0
  57. package/pack/generated/claude/skills/overhaul/roles/backend-engineer.md +61 -0
  58. package/pack/generated/claude/skills/overhaul/roles/boundary.md +56 -0
  59. package/pack/generated/claude/skills/overhaul/roles/craft.md +77 -0
  60. package/pack/generated/claude/skills/overhaul/roles/frontend-engineer.md +63 -0
  61. package/pack/generated/claude/skills/overhaul/roles/implementer.md +59 -0
  62. package/pack/generated/claude/skills/overhaul/roles/recon.md +60 -0
  63. package/pack/generated/claude/skills/overhaul/roles/specialist.md +56 -0
  64. package/pack/generated/claude/skills/overhaul/roles/verifier.md +60 -0
  65. package/pack/generated/codex/skills/overhaul/SKILL.md +201 -0
  66. package/pack/generated/codex/skills/overhaul/agents/openai.yaml +2 -0
  67. package/pack/generated/codex/skills/overhaul/references/audit-domains.md +263 -0
  68. package/pack/generated/codex/skills/overhaul/references/lifecycle.md +308 -0
  69. package/pack/generated/codex/skills/overhaul/references/orchestration.md +264 -0
  70. package/pack/generated/codex/skills/overhaul/references/records.md +137 -0
  71. package/pack/generated/codex/skills/overhaul/references/repair-and-measurement.md +207 -0
  72. package/pack/generated/codex/skills/overhaul/references/review-and-report.md +137 -0
  73. package/pack/generated/codex/skills/overhaul/references/scope-and-safety.md +191 -0
  74. package/pack/generated/codex/skills/overhaul/references/scoring.md +222 -0
  75. package/pack/generated/codex/skills/overhaul/references/stack-profiles.md +264 -0
  76. package/pack/generated/codex/skills/overhaul/roles/backend-engineer.md +61 -0
  77. package/pack/generated/codex/skills/overhaul/roles/boundary.md +56 -0
  78. package/pack/generated/codex/skills/overhaul/roles/craft.md +77 -0
  79. package/pack/generated/codex/skills/overhaul/roles/frontend-engineer.md +63 -0
  80. package/pack/generated/codex/skills/overhaul/roles/implementer.md +59 -0
  81. package/pack/generated/codex/skills/overhaul/roles/recon.md +60 -0
  82. package/pack/generated/codex/skills/overhaul/roles/specialist.md +56 -0
  83. package/pack/generated/codex/skills/overhaul/roles/verifier.md +60 -0
  84. package/pack/generated/devin/skills/overhaul/SKILL.md +201 -0
  85. package/pack/generated/devin/skills/overhaul/references/audit-domains.md +263 -0
  86. package/pack/generated/devin/skills/overhaul/references/lifecycle.md +308 -0
  87. package/pack/generated/devin/skills/overhaul/references/orchestration.md +264 -0
  88. package/pack/generated/devin/skills/overhaul/references/records.md +137 -0
  89. package/pack/generated/devin/skills/overhaul/references/repair-and-measurement.md +207 -0
  90. package/pack/generated/devin/skills/overhaul/references/review-and-report.md +137 -0
  91. package/pack/generated/devin/skills/overhaul/references/scope-and-safety.md +191 -0
  92. package/pack/generated/devin/skills/overhaul/references/scoring.md +222 -0
  93. package/pack/generated/devin/skills/overhaul/references/stack-profiles.md +264 -0
  94. package/pack/generated/devin/skills/overhaul/roles/backend-engineer.md +61 -0
  95. package/pack/generated/devin/skills/overhaul/roles/boundary.md +56 -0
  96. package/pack/generated/devin/skills/overhaul/roles/craft.md +77 -0
  97. package/pack/generated/devin/skills/overhaul/roles/frontend-engineer.md +63 -0
  98. package/pack/generated/devin/skills/overhaul/roles/implementer.md +59 -0
  99. package/pack/generated/devin/skills/overhaul/roles/recon.md +60 -0
  100. package/pack/generated/devin/skills/overhaul/roles/specialist.md +56 -0
  101. package/pack/generated/devin/skills/overhaul/roles/verifier.md +60 -0
  102. package/pack/generated/omp/skills/overhaul/SKILL.md +201 -0
  103. package/pack/generated/omp/skills/overhaul/references/audit-domains.md +263 -0
  104. package/pack/generated/omp/skills/overhaul/references/lifecycle.md +308 -0
  105. package/pack/generated/omp/skills/overhaul/references/orchestration.md +264 -0
  106. package/pack/generated/omp/skills/overhaul/references/records.md +137 -0
  107. package/pack/generated/omp/skills/overhaul/references/repair-and-measurement.md +207 -0
  108. package/pack/generated/omp/skills/overhaul/references/review-and-report.md +137 -0
  109. package/pack/generated/omp/skills/overhaul/references/scope-and-safety.md +191 -0
  110. package/pack/generated/omp/skills/overhaul/references/scoring.md +222 -0
  111. package/pack/generated/omp/skills/overhaul/references/stack-profiles.md +264 -0
  112. package/pack/generated/omp/skills/overhaul/roles/backend-engineer.md +61 -0
  113. package/pack/generated/omp/skills/overhaul/roles/boundary.md +56 -0
  114. package/pack/generated/omp/skills/overhaul/roles/craft.md +77 -0
  115. package/pack/generated/omp/skills/overhaul/roles/frontend-engineer.md +63 -0
  116. package/pack/generated/omp/skills/overhaul/roles/implementer.md +59 -0
  117. package/pack/generated/omp/skills/overhaul/roles/recon.md +60 -0
  118. package/pack/generated/omp/skills/overhaul/roles/specialist.md +56 -0
  119. package/pack/generated/omp/skills/overhaul/roles/verifier.md +60 -0
  120. package/pack/generated/pi/skills/overhaul/SKILL.md +201 -0
  121. package/pack/generated/pi/skills/overhaul/references/audit-domains.md +263 -0
  122. package/pack/generated/pi/skills/overhaul/references/lifecycle.md +308 -0
  123. package/pack/generated/pi/skills/overhaul/references/orchestration.md +264 -0
  124. package/pack/generated/pi/skills/overhaul/references/records.md +137 -0
  125. package/pack/generated/pi/skills/overhaul/references/repair-and-measurement.md +207 -0
  126. package/pack/generated/pi/skills/overhaul/references/review-and-report.md +137 -0
  127. package/pack/generated/pi/skills/overhaul/references/scope-and-safety.md +191 -0
  128. package/pack/generated/pi/skills/overhaul/references/scoring.md +222 -0
  129. package/pack/generated/pi/skills/overhaul/references/stack-profiles.md +264 -0
  130. package/pack/generated/pi/skills/overhaul/roles/backend-engineer.md +61 -0
  131. package/pack/generated/pi/skills/overhaul/roles/boundary.md +56 -0
  132. package/pack/generated/pi/skills/overhaul/roles/craft.md +77 -0
  133. package/pack/generated/pi/skills/overhaul/roles/frontend-engineer.md +63 -0
  134. package/pack/generated/pi/skills/overhaul/roles/implementer.md +59 -0
  135. package/pack/generated/pi/skills/overhaul/roles/recon.md +60 -0
  136. package/pack/generated/pi/skills/overhaul/roles/specialist.md +56 -0
  137. package/pack/generated/pi/skills/overhaul/roles/verifier.md +60 -0
  138. package/package.json +1 -1
@@ -0,0 +1,392 @@
1
+ // Package records implements the /overhaul records tool: generation staging
2
+ // and publishing plus cross-record validation of a run area.
3
+ //
4
+ // digest FILE sha256 of exact bytes
5
+ // stage RUN copy current generation to RUN/g<N+1>.tmp (prints path)
6
+ // publish RUN validate staged generation, write manifest, swap CURRENT last
7
+ // validate RUN verify CURRENT generation integrity + cross-record rules
8
+ //
9
+ // Exit: 0 ok, 1 rule violations (listed), 2 usage/IO error.
10
+ package records
11
+
12
+ import (
13
+ "fmt"
14
+ "io"
15
+ "io/fs"
16
+ "os"
17
+ "path/filepath"
18
+ "regexp"
19
+ "sort"
20
+ "strconv"
21
+ "strings"
22
+
23
+ "github.com/devrites/devrites/internal/overhaul/ovio"
24
+ )
25
+
26
+ const usage = `usage: overhaul records <command> ...
27
+
28
+ digest FILE sha256 of exact bytes
29
+ stage RUN copy current generation to RUN/g<N+1>.tmp (prints path)
30
+ publish RUN validate staged generation, write manifest, swap CURRENT last
31
+ validate RUN verify CURRENT generation integrity + cross-record rules
32
+ Exit: 0 ok, 1 rule violations (listed), 2 usage/IO error.
33
+ `
34
+
35
+ // Run executes the tool with its arguments (tool name excluded).
36
+ func Run(args []string, stdout, stderr io.Writer) int {
37
+ if len(args) < 2 {
38
+ fmt.Fprint(stderr, usage)
39
+ return 2
40
+ }
41
+ var code int
42
+ var err error
43
+ switch args[0] {
44
+ case "digest":
45
+ var d string
46
+ if d, err = sha(args[1]); err == nil {
47
+ fmt.Fprintln(stdout, d)
48
+ }
49
+ case "stage":
50
+ code, err = stage(args[1], stdout, stderr)
51
+ case "publish":
52
+ code, err = publish(args[1], stdout, stderr)
53
+ case "validate":
54
+ code, err = validate(args[1], stdout)
55
+ default:
56
+ fmt.Fprint(stderr, usage)
57
+ return 2
58
+ }
59
+ if err != nil {
60
+ fmt.Fprintf(stderr, "ERROR: %v\n", err)
61
+ return 2
62
+ }
63
+ return code
64
+ }
65
+
66
+ // current returns the CURRENT generation name; ok is false when there is none.
67
+ func current(run string) (string, bool, error) {
68
+ p := join(run, "CURRENT")
69
+ if !isFile(p) {
70
+ return "", false, nil
71
+ }
72
+ b, err := os.ReadFile(p) // #nosec G304 -- path is an explicit operator argument
73
+ return strings.TrimSpace(string(b)), err == nil, err
74
+ }
75
+
76
+ func genNumber(gen string) (int, error) {
77
+ if gen == "" {
78
+ return 0, fmt.Errorf("invalid generation name %q", gen)
79
+ }
80
+ n, err := strconv.Atoi(gen[1:])
81
+ if err != nil {
82
+ return 0, fmt.Errorf("invalid generation name %q", gen)
83
+ }
84
+ return n, nil
85
+ }
86
+
87
+ func sortedKeys(m map[string]any) []string {
88
+ keys := make([]string, 0, len(m))
89
+ for k := range m {
90
+ keys = append(keys, k)
91
+ }
92
+ sort.Strings(keys)
93
+ return keys
94
+ }
95
+
96
+ func manifestOK(run, genDir string) ([]string, map[string]any, error) {
97
+ m, err := ovio.LoadObject(join(genDir, "manifest.json"))
98
+ if err != nil {
99
+ return nil, nil, err
100
+ }
101
+ var errs []string
102
+ files, revs := ovio.Obj(m["files"]), ovio.Obj(m["revisions"])
103
+ check := func(src map[string]any) error {
104
+ for _, rel := range sortedKeys(src) {
105
+ base := run
106
+ if _, ok := files[rel]; ok {
107
+ base = genDir
108
+ }
109
+ p := join(base, rel)
110
+ ok := isFile(p)
111
+ if ok {
112
+ d, err := sha(p)
113
+ if err != nil {
114
+ return err
115
+ }
116
+ ok = eq(d, src[rel])
117
+ }
118
+ if !ok {
119
+ errs = append(errs, fmt.Sprintf("mixed or modified generation: %s does not match manifest", rel))
120
+ }
121
+ }
122
+ return nil
123
+ }
124
+ if err := check(files); err != nil {
125
+ return nil, nil, err
126
+ }
127
+ if err := check(revs); err != nil {
128
+ return nil, nil, err
129
+ }
130
+ return errs, m, nil
131
+ }
132
+
133
+ // copyTree is shutil.copytree(src, dst, ignore=ignore_patterns("manifest.json", "views")):
134
+ // symlinks are followed and the two names are skipped at every level.
135
+ func copyTree(src, dst string) error {
136
+ fi, err := os.Stat(src)
137
+ if err != nil {
138
+ return err
139
+ }
140
+ if err := os.MkdirAll(dst, fi.Mode().Perm()); err != nil {
141
+ return err
142
+ }
143
+ entries, err := os.ReadDir(src)
144
+ if err != nil {
145
+ return err
146
+ }
147
+ for _, e := range entries {
148
+ if e.Name() == "manifest.json" || e.Name() == "views" {
149
+ continue
150
+ }
151
+ s, d := filepath.Join(src, e.Name()), filepath.Join(dst, e.Name())
152
+ st, err := os.Stat(s)
153
+ if err != nil {
154
+ return err
155
+ }
156
+ if st.IsDir() {
157
+ err = copyTree(s, d)
158
+ } else {
159
+ // #nosec G304 G703 -- copies a record between directories inside the operator-selected run area
160
+ b, rerr := os.ReadFile(s)
161
+ if err = rerr; err == nil {
162
+ // #nosec G703 -- destination is the staged generation inside the run area
163
+ err = os.WriteFile(d, b, st.Mode().Perm())
164
+ }
165
+ }
166
+ if err != nil {
167
+ return err
168
+ }
169
+ }
170
+ return nil
171
+ }
172
+
173
+ func stage(run string, stdout, stderr io.Writer) (int, error) {
174
+ if err := os.MkdirAll(join(run, "revisions"), 0o700); err != nil {
175
+ return 0, err
176
+ }
177
+ cur, has, err := current(run)
178
+ if err != nil {
179
+ return 0, err
180
+ }
181
+ n, curName := 1, "None"
182
+ if has {
183
+ curName = cur
184
+ }
185
+ if cur != "" {
186
+ g, err := genNumber(cur)
187
+ if err != nil {
188
+ return 0, err
189
+ }
190
+ n = g + 1
191
+ }
192
+ final := join(run, fmt.Sprintf("g%04d", n))
193
+ tmp := final + ".tmp"
194
+ if _, err := os.Stat(final); err == nil {
195
+ fmt.Fprintf(stderr, "g%04d exists but CURRENT is %s: interrupted publish; inspect it, then repair CURRENT or move it aside\n", n, curName)
196
+ return 2, nil
197
+ }
198
+ if _, err := os.Stat(tmp); err == nil {
199
+ fmt.Fprintf(stderr, "staged generation already exists: %s\n", tmp)
200
+ return 2, nil
201
+ }
202
+ base := "none"
203
+ if cur != "" {
204
+ base = cur
205
+ err = copyTree(join(run, cur), tmp)
206
+ } else {
207
+ err = os.MkdirAll(tmp, 0o700)
208
+ }
209
+ if err != nil {
210
+ return 0, err
211
+ }
212
+ if err := os.WriteFile(join(tmp, ".base"), []byte(base), 0o600); err != nil {
213
+ return 0, err
214
+ }
215
+ fmt.Fprintln(stdout, tmp)
216
+ return 0, nil
217
+ }
218
+
219
+ var stagedName = regexp.MustCompile(`^g[0-9]{4}\.tmp$`)
220
+
221
+ func publish(run string, stdout, stderr io.Writer) (int, error) {
222
+ entries, err := os.ReadDir(run)
223
+ if err != nil {
224
+ return 0, err
225
+ }
226
+ var staged []string
227
+ for _, e := range entries {
228
+ if stagedName.MatchString(e.Name()) {
229
+ staged = append(staged, e.Name())
230
+ }
231
+ }
232
+ if len(staged) != 1 {
233
+ fmt.Fprintf(stderr, "expected exactly one staged generation, found %s\n", Repr(staged))
234
+ return 2, nil
235
+ }
236
+ tmp := join(run, staged[0])
237
+ prev, hasPrev, err := current(run)
238
+ if err != nil {
239
+ return 0, err
240
+ }
241
+ b, err := os.ReadFile(join(tmp, ".base"))
242
+ if err != nil {
243
+ return 0, err
244
+ }
245
+ want, prevName := "none", "None"
246
+ if hasPrev {
247
+ prevName = prev
248
+ }
249
+ if prev != "" {
250
+ want = prev
251
+ }
252
+ if base := strings.TrimSpace(string(b)); base != want {
253
+ fmt.Fprintf(stderr, "stale stage: CURRENT moved from %s to %s; re-stage and reapply\n", base, prevName)
254
+ return 1, nil
255
+ }
256
+ prevDir := ""
257
+ if prev != "" {
258
+ prevDir = join(run, prev)
259
+ }
260
+ errs, err := Check(run, tmp, prevDir)
261
+ if err != nil {
262
+ return 0, err
263
+ }
264
+ revs := map[string]any{}
265
+ if prevDir != "" {
266
+ pmErrs, pm, err := manifestOK(run, prevDir)
267
+ if err != nil {
268
+ return 0, err
269
+ }
270
+ errs = append(errs, pmErrs...)
271
+ prevRevs := ovio.Obj(pm["revisions"])
272
+ for _, rel := range sortedKeys(prevRevs) {
273
+ p := join(run, rel)
274
+ ok := isFile(p)
275
+ if ok {
276
+ d, err := sha(p)
277
+ if err != nil {
278
+ return 0, err
279
+ }
280
+ ok = eq(d, prevRevs[rel])
281
+ }
282
+ if !ok {
283
+ errs = append(errs, "immutable revision changed: "+rel)
284
+ }
285
+ revs[rel] = prevRevs[rel]
286
+ }
287
+ }
288
+ revEntries, err := os.ReadDir(join(run, "revisions"))
289
+ if err != nil {
290
+ return 0, err
291
+ }
292
+ for _, e := range revEntries {
293
+ d, err := sha(join(run, "revisions", e.Name()))
294
+ if err != nil {
295
+ return 0, err
296
+ }
297
+ revs["revisions/"+e.Name()] = d
298
+ }
299
+ if len(errs) > 0 {
300
+ for _, e := range errs {
301
+ fmt.Fprintln(stdout, "VIOLATION: "+e)
302
+ }
303
+ return 1, nil
304
+ }
305
+ if err := os.Remove(join(tmp, ".base")); err != nil {
306
+ return 0, err
307
+ }
308
+ files := map[string]any{}
309
+ err = filepath.WalkDir(tmp, func(p string, d fs.DirEntry, err error) error {
310
+ if err != nil || d.IsDir() {
311
+ return err
312
+ }
313
+ if d.Type()&fs.ModeSymlink != 0 && isDir(p) {
314
+ return nil // os.walk lists a symlinked directory but never descends into it
315
+ }
316
+ rel, err := filepath.Rel(tmp, p)
317
+ if err != nil {
318
+ return err
319
+ }
320
+ digest, err := sha(p)
321
+ files[filepath.ToSlash(rel)] = digest
322
+ return err
323
+ })
324
+ if err != nil {
325
+ return 0, err
326
+ }
327
+ gen := strings.TrimSuffix(staged[0], ".tmp")
328
+ var previous any
329
+ if hasPrev {
330
+ previous = prev
331
+ }
332
+ manifest := map[string]any{"schema": "overhaul.manifest/1", "generation": gen, "previous": previous,
333
+ "files": files, "revisions": revs}
334
+ if err := ovio.WriteJSON(join(tmp, "manifest.json"), manifest, 0o666); err != nil {
335
+ return 0, err
336
+ }
337
+ if err := os.Rename(tmp, join(run, gen)); err != nil {
338
+ return 0, err
339
+ }
340
+ if err := os.WriteFile(join(run, "CURRENT.tmp"), []byte(gen+"\n"), 0o600); err != nil {
341
+ return 0, err
342
+ }
343
+ if err := os.Rename(join(run, "CURRENT.tmp"), join(run, "CURRENT")); err != nil {
344
+ return 0, err
345
+ }
346
+ fmt.Fprintln(stdout, "published "+gen)
347
+ return 0, nil
348
+ }
349
+
350
+ func validate(run string, stdout io.Writer) (int, error) {
351
+ b, err := os.ReadFile(join(run, "CURRENT"))
352
+ if err != nil {
353
+ return 0, err
354
+ }
355
+ gen := strings.TrimSpace(string(b))
356
+ genDir := join(run, gen)
357
+ errs, m, err := manifestOK(run, genDir)
358
+ if err != nil {
359
+ return 0, err
360
+ }
361
+ prevDir := ""
362
+ if p := m["previous"]; ovio.Truthy(p) {
363
+ prevDir = join(run, PyStr(p))
364
+ }
365
+ more, err := Check(run, genDir, prevDir)
366
+ if err != nil {
367
+ return 0, err
368
+ }
369
+ errs = append(errs, more...)
370
+ n, err := genNumber(gen)
371
+ if err != nil {
372
+ return 0, err
373
+ }
374
+ entries, err := os.ReadDir(run)
375
+ if err != nil {
376
+ return 0, err
377
+ }
378
+ next := fmt.Sprintf("g%04d", n+1)
379
+ for _, e := range entries {
380
+ if d := e.Name(); strings.HasSuffix(d, ".tmp") || d == next {
381
+ fmt.Fprintf(stdout, "NOTE: %s is newer than CURRENT (crash or in-progress publish)\n", d)
382
+ }
383
+ }
384
+ if len(errs) > 0 {
385
+ for _, e := range errs {
386
+ fmt.Fprintln(stdout, "VIOLATION: "+e)
387
+ }
388
+ return 1, nil
389
+ }
390
+ fmt.Fprintln(stdout, "valid "+gen)
391
+ return 0, nil
392
+ }