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
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
+ ## [5.13.1](https://github.com/ViktorsBaikers/DevRites/compare/v5.13.0...v5.13.1) (2026-09-25)
6
+
7
+ ### Fixed
8
+
9
+ * **uninstall:** keep global engine binary when DEVRITES_NO_BINARY=1 ([20bcfcb](https://github.com/ViktorsBaikers/DevRites/commit/20bcfcb887fbf10944daf0f916b9fcce019deff4))
10
+
11
+ ## [5.13.0](https://github.com/ViktorsBaikers/DevRites/compare/v5.12.0...v5.13.0) (2026-09-25)
12
+
13
+ ### Added
14
+
15
+ * **skills:** add approval-gated /overhaul skill with engine tools ([#82](https://github.com/ViktorsBaikers/DevRites/issues/82)) ([79d0af0](https://github.com/ViktorsBaikers/DevRites/commit/79d0af0518d2536c5fd9278ca4891c95f4576a59))
16
+
5
17
  ## [5.12.0](https://github.com/ViktorsBaikers/DevRites/compare/v5.11.0...v5.12.0) (2026-09-20)
6
18
 
7
19
  ### Added
package/README.md CHANGED
@@ -34,7 +34,7 @@ project-conventional push, tag, or PR action, and archive the workspace.
34
34
  Unattended runs may create local WIP checkpoint commits along the way, but they
35
35
  remain local unless Ship's disclosed plan includes an approved remote action.
36
36
 
37
- **Status:** [`v5.12.0`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v5.12.0): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
37
+ **Status:** [`v5.13.1`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v5.13.1): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
38
38
 
39
39
  This is the latest published release; `main` may contain unreleased work.
40
40
 
@@ -423,8 +423,10 @@ through Claude Code or Codex plugin stores.
423
423
 
424
424
  ## Skills and agents
425
425
 
426
- The pack ships 44 skills: 33 public and 11 internal. The public surface contains
427
- the `rite` menu and 32 `rite-*` workflows and utilities. Ten `devrites-*`
426
+ The pack ships 45 skills: 34 public and 11 internal. The public surface contains
427
+ the `rite` menu, 32 `rite-*` workflows and utilities, and the standalone,
428
+ explicit-only `/overhaul` (approval-gated project, PR or branch audit with verified
429
+ repairs; it runs without a DevRites workspace). Ten `devrites-*`
428
430
  specialists load when a matching task needs them; `devrites-lib` carries the
429
431
  shared contracts and engineering standards.
430
432
 
@@ -389,8 +389,8 @@ contract.
389
389
 
390
390
  ## Design choices at a glance
391
391
 
392
- - **Surface**: 32 public `rite-*` skills (44 total), plus the thin `/rite`
393
- menu: 33 public and 11 internal. The lifecycle
392
+ - **Surface**: 32 public `rite-*` skills (45 total), plus the thin `/rite`
393
+ menu and the standalone `overhaul` skill: 34 public and 11 internal. The lifecycle
394
394
  includes mandatory adaptive Clarify, optional Temper, mandatory Vet, and
395
395
  Converge recovery; Seal **decides** and Ship **executes + closes**. Ten
396
396
  `devrites-*` specialist skills are model-invoked; `devrites-lib` is the
@@ -22,7 +22,8 @@ visibility. Public utilities use the `rite-*` prefix: `rite-quick`,
22
22
  `rite-autocomplete`, `rite-explain`, `rite-pov`, `rite-dogfood`,
23
23
  `rite-pr-feedback`, and `rite-watch-pr`, plus the evidence-gated workspace compatibility command
24
24
  `rite-upgrade`. The host may invoke `devrites-*` specialists through the model.
25
- `devrites-lib` is the non-workflow library exception.
25
+ `devrites-lib` is the non-workflow library exception. `overhaul` is a standalone,
26
+ explicit-only skill outside both prefixes; it runs without any DevRites workspace.
26
27
 
27
28
  ## Surface lifecycle
28
29
 
@@ -121,6 +122,7 @@ See [`cli.md`](cli.md).
121
122
  | [`/rite-watch-pr`](../pack/.claude/skills/rite-watch-pr/SKILL.md) | utility | `[PR number\|PR URL\|blank for current branch]` | Observe one GitHub PR/CI/review snapshot, classify one next action, and stop. Safe for capability-admitted native schedules/events; never mutates code, Git, checks, threads, or PR state. | PR/check/review metadata + bounded failed-log excerpts | none |
122
123
  | [`/rite-pressure-test`](../pack/.claude/skills/rite-pressure-test/SKILL.md) | utility | `[idea]` | Pressure-test a rough idea: 3 to 5 genuinely different options → converge on one with trade-off + hinge. | spec / surrounding code | `decisions.md` (optional) |
123
124
  | [`/rite-doctor`](../pack/.claude/skills/rite-doctor/SKILL.md) | diagnostic | none | Diagnose the DevRites binary, pack, schema, and native host permission/profile configuration. | install + host config | none |
125
+ | [`/overhaul`](../pack/.claude/skills/overhaul/SKILL.md) | standalone | `[full \| pr <n\|url> \| branch <head> --base <base> \| audit <scope> \| apply <run-id> --plan <rev> \| status <run-id> \| resume <run-id>]` | Explicit-only, outside the lifecycle. Concurrent frontend and backend engineering review plus boundary, craft and specialist roles (skill-local role files dispatched as fresh contexts); one verified plan; stops for the user's approval of the exact plan revision; then test-first repairs, independent verification, measurement and evidence-backed scoring, cycling inside the approved envelope. `audit` never repairs. Never commits, pushes, stashes or touches the index. | target repository (read-only until approval) | run area outside the repository (JSON records, HTML/Markdown views, evidence); approved target paths only after approval |
124
126
 
125
127
  `/rite use <slug>` and `/rite guide` are inline router operations owned by
126
128
  `/rite`, not separate skills. There is no `/rite-use` or `/rite-guide` shortcut.
@@ -46,6 +46,7 @@ workflow strategy.
46
46
  | `secret-scan [--staged] [--stdin] [slug]` | Scan exact staged blobs, stdin, or touched regular files for credential material. |
47
47
  | `open-visual <path-or-name> [--slug <slug>] [--no-open]` | Resolve a local visual HTML file, optionally open it in the OS browser, warn if the sibling outline is missing or inventory ids are absent from HTML, and print agent path tips. No network. |
48
48
  | `detect commands [--root <dir>] [--json]` | Resolve the repository's own test/lint/vet/build commands: explicit Makefile targets first, then `package.json` scripts, then language-manifest fallbacks, plus the lockfile-derived package manager. Read-only, no installs, no execution. |
49
+ | `overhaul <records\|admit\|snapshot\|score\|bench\|render> ...` | Deterministic tools behind the standalone `/overhaul` skill: run-record generations and cross-record validation, worker-receipt admission and quote anchoring, index-safe baseline snapshots and per-attempt deltas, exact-rational readiness scoring, benchmark ratio intervals, and escaped offline review/report views. Operates only on explicit run-area and repository paths. See [Overhaul tools](#overhaul-tools). |
49
50
  | `version` | Print the engine version. |
50
51
 
51
52
  `help`, `-h`, and `--help` print this operational inventory. Each operational
@@ -136,6 +137,26 @@ the local file only — never a network fetch. Stdout prints the absolute HTML
136
137
  path, outline path tip, playbook index hint, and an `ids=ok` / `ids=mismatch`
137
138
  summary when an inventory is present.
138
139
 
140
+ ## Overhaul tools
141
+
142
+ `overhaul` serves the explicit-only `/overhaul` skill. Each tool takes explicit
143
+ paths and never resolves a `.devrites` workspace, calls a model or uses the
144
+ network. The skill documents the record schemas and when to call each tool.
145
+
146
+ | Tool | Forms | Exit codes |
147
+ | --- | --- | --- |
148
+ | `records` | `digest <file>`, `stage <run>`, `publish <run>`, `validate <run>` | 0 ok; 1 violations (listed as `VIOLATION:` lines); 2 usage or I/O |
149
+ | `admit` | `receipt <run> <receipt.json> [--observed <file>]`, `anchor <tree> <proposals.json>` | 0 admissible; 1 rejected; 2 usage or I/O; 3 duplicate or late receipt |
150
+ | `snapshot` | `capture <repo> <out>`, `fingerprint <repo>`, `verify <repo> <out> [--agent-paths <file>]`, `state <repo> <out.json>`, `delta <before.json> <repo>` | 0 ok; 1 index changed or changes outside agent paths; 2 refusal, usage or I/O |
151
+ | `score` | `--rubric <r> --results <s> --gates <g> [--out <file>]`, `compare --rubric <r> --baseline <a> --candidate <b>` | 0 computed; 2 invalid input (nothing counts as scored) |
152
+ | `bench` | `<result.json>` | 0 computed; 2 invalid input |
153
+ | `render` | `<run> <staged-generation> <review\|report>` | 0 written; 2 usage, invalid records or I/O |
154
+
155
+ `snapshot` runs git read-only with `GIT_OPTIONAL_LOCKS=0`,
156
+ `diff.autoRefreshIndex=false` and no external diff drivers, so `.git/index`
157
+ stays byte-identical. Secret-pattern files and files holding private key material
158
+ are hashed only: never copied into the snapshot tree and never included in its patches.
159
+
139
160
  ## Output and exit contracts
140
161
 
141
162
  `check candidate` passes with exactly:
package/docs/flow.md CHANGED
@@ -381,7 +381,7 @@ separately by `disable-model-invocation`.
381
381
 
382
382
  ```mermaid
383
383
  flowchart TB
384
- subgraph Public["Public (user-invocable: true): 33 skills"]
384
+ subgraph Public["Public (user-invocable: true): 34 skills"]
385
385
  direction TB
386
386
  R1[/rite/]
387
387
  R2[/rite-spec/]
@@ -416,6 +416,7 @@ flowchart TB
416
416
  D1[/rite-zoom-out/]
417
417
  D2[/rite-prototype/]
418
418
  D3[/rite-handoff/]
419
+ OVH[/overhaul<br/>standalone, outside the lifecycle/]
419
420
  end
420
421
  subgraph Internal["Internal (user-invocable: false): 11 skills (10 specialists + devrites-lib library)"]
421
422
  direction TB
package/docs/skills.md CHANGED
@@ -1,8 +1,8 @@
1
- # All 44 skills
1
+ # All 45 skills
2
2
 
3
- The pack contains 44 skills: the `rite` menu, 32 user-invocable `rite-*`
4
- workflow and utility skills, 10 model-invoked `devrites-*` specialists, and the
5
- internal `devrites-lib` library. `devrites-lib` is not a command. It holds shared
3
+ The pack contains 45 skills: the `rite` menu, 32 user-invocable `rite-*`
4
+ workflow and utility skills, the standalone `overhaul` skill,
5
+ 10 model-invoked `devrites-*` specialists, and the internal `devrites-lib` library. `devrites-lib` is not a command. It holds shared
6
6
  references and the few explicit script exceptions.
7
7
 
8
8
  Install DevRites through npm with `npx devrites ...`. DevRites is not available
@@ -49,6 +49,9 @@ contract when a rite names parallel reviewers.
49
49
  Some specialized utilities set `disable-model-invocation: true` and run only
50
50
  when explicitly invoked, which keeps the always-loaded skill surface small.
51
51
  `devrites-*` is the specialist and library namespace used to avoid collisions.
52
+ `overhaul` sits outside both namespaces on purpose: it is a standalone,
53
+ explicit-only skill that needs no DevRites workspace or lifecycle; its deterministic
54
+ tools run as `devrites-engine overhaul` commands.
52
55
  The `user-invocable:` and `disable-model-invocation:` fields, not the prefix,
53
56
  set visibility and invocation policy. [`command-map.md`](command-map.md)
54
57
  catalogs the effective values.
@@ -239,6 +242,12 @@ and `Shipped`. Utility commands keep the same compact labels and one-next-action
239
242
  | [`rite-pr-feedback`](../pack/.claude/skills/rite-pr-feedback/SKILL.md) | Resolve PR review feedback: fetch unresolved threads, judge centrally, fix valid items, reply, resolve. | Explicit-only: `/rite-pr-feedback` / `/rite pr-feedback`. |
240
243
  | [`rite-watch-pr`](../pack/.claude/skills/rite-watch-pr/SKILL.md) | Observe one PR/CI/review snapshot and report one next action without mutation. | Read-only capability-admitted native schedule/event observation or `/rite-watch-pr`. |
241
244
 
245
+ ### Standalone: approval-gated overhaul
246
+
247
+ | Skill | What It Does | Use When |
248
+ |---|---|---|
249
+ | [`overhaul`](../pack/.claude/skills/overhaul/SKILL.md) | Audits a whole project, a PR or a branch comparison with separate frontend and backend engineering reviewers running concurrently, plus boundary, craft and specialist roles; reconciles verified findings into one plan and stops for the user's explicit approval of that exact plan revision. After approval it repairs test-first, verifies independently, measures, and keeps cycling inside the approved plan until the evidence-backed scores (global and per-lane 9.7, domains 9.0) and every hard gate pass or an honest blocker remains. It lives outside the DevRites lifecycle: no `.devrites/` workspace and no `/rite-*` phase; its records, admission, snapshot, scoring, benchmark and view tools are `devrites-engine overhaul` commands. Completion evidence is a run area outside the repository with canonical JSON records, a local HTML review and report, and a reviewable local patch; it never commits. | Explicit-only: `/overhaul`, `/overhaul full`, `/overhaul pr <n>`, `/overhaul branch <head> --base <base>`, `/overhaul audit …` (assessment only). |
250
+
242
251
  ### Foundation: engineering rules
243
252
 
244
253
  | Skill | What It Does | Use When |
package/engine/help.go CHANGED
@@ -5,6 +5,7 @@ import (
5
5
  "io"
6
6
  "strings"
7
7
 
8
+ "github.com/devrites/devrites/internal/overhaul"
8
9
  "github.com/devrites/devrites/internal/parallel"
9
10
  )
10
11
 
@@ -136,7 +137,7 @@ func shouldPrintHelp(args []string) bool {
136
137
  return true
137
138
  }
138
139
  switch args[0] {
139
- case "check", "state", "observe", "parallel", "gates", "claim", "detect", "note", "metrics", "handoff", "context", "orient", "migrate":
140
+ case "check", "state", "observe", "parallel", "gates", "claim", "detect", "note", "metrics", "handoff", "context", "orient", "migrate", "overhaul":
140
141
  return len(args) >= 2 && args[1] == "help"
141
142
  default:
142
143
  return false
@@ -194,6 +195,8 @@ func commandHelp(args []string) (string, bool) {
194
195
  return gatesFamilyUsage, true
195
196
  case "note":
196
197
  return noteFamilyUsage, true
198
+ case "overhaul":
199
+ return overhaul.Usage, true
197
200
  case "migrate":
198
201
  return migrateUsage, true
199
202
  case "secret-scan":
@@ -47,6 +47,8 @@ func TestCommandHelp(t *testing.T) {
47
47
  {args: []string{"migrate", "--help"}, want: "migrate <slug>"},
48
48
  {args: []string{"secret-scan", "--help"}, want: "secret-scan [--staged] [--stdin]"},
49
49
  {args: []string{"open-visual", "--help"}, want: "open-visual <path-or-name>"},
50
+ {args: []string{"overhaul", "--help"}, want: "overhaul records validate <run>"},
51
+ {args: []string{"overhaul", "help"}, want: "overhaul snapshot capture <repo> <out>"},
50
52
  {args: []string{"version", "--help"}, want: "devrites-engine version"},
51
53
  {args: []string{"install", "--help"}, want: "usage: devrites-engine install"},
52
54
  {args: []string{"update", "--help"}, want: "usage: devrites-engine update"},
@@ -167,6 +167,10 @@ func (r *runner) removeBinary() error {
167
167
  fmt.Fprintln(r.opts.Stdout, " kept the global devrites-engine binary (--keep-binary).")
168
168
  return nil
169
169
  }
170
+ if os.Getenv("DEVRITES_NO_BINARY") == "1" {
171
+ fmt.Fprintln(r.opts.Stdout, " kept the global devrites-engine binary (DEVRITES_NO_BINARY=1).")
172
+ return nil
173
+ }
170
174
  for _, p := range binaryCandidates() {
171
175
  if p == "" || !exists(p) {
172
176
  continue
@@ -1231,6 +1231,28 @@ func runInstall(t *testing.T, target, payload string, mutate func(*Options)) {
1231
1231
  }
1232
1232
  }
1233
1233
 
1234
+ func TestUninstallNoBinaryEnvKeepsGlobalBinary(t *testing.T) {
1235
+ t.Setenv("DEVRITES_NO_BINARY", "1")
1236
+ home := t.TempDir()
1237
+ t.Setenv("HOME", home)
1238
+ t.Setenv("DEVRITES_BIN_DIR", t.TempDir())
1239
+ bin := filepath.Join(home, ".local", "bin", engineBinaryName())
1240
+ testutil.WriteFile(t, bin, "engine\n")
1241
+ target := t.TempDir()
1242
+ runInstall(t, target, testPayload(t), func(*Options) {})
1243
+
1244
+ opts := DefaultOptions(ModeUninstall)
1245
+ opts.Target = target
1246
+ opts.Stdout = &bytes.Buffer{}
1247
+ opts.Stderr = &bytes.Buffer{}
1248
+ if err := Apply(opts); err != nil {
1249
+ t.Fatal(err)
1250
+ }
1251
+ if _, err := os.Stat(bin); err != nil {
1252
+ t.Fatalf("uninstall with DEVRITES_NO_BINARY=1 removed global binary: %v", err)
1253
+ }
1254
+ }
1255
+
1234
1256
  func runUninstall(t *testing.T, target string) {
1235
1257
  t.Helper()
1236
1258
  opts := DefaultOptions(ModeUninstall)
@@ -0,0 +1,170 @@
1
+ package admit
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
9
+ "testing"
10
+ )
11
+
12
+ type J = map[string]any
13
+ type L = []any
14
+
15
+ func write(t *testing.T, path string, v any) string {
16
+ t.Helper()
17
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
18
+ t.Fatal(err)
19
+ }
20
+ b, ok := v.(string)
21
+ if !ok {
22
+ raw, err := json.Marshal(v)
23
+ if err != nil {
24
+ t.Fatal(err)
25
+ }
26
+ b = string(raw)
27
+ }
28
+ if err := os.WriteFile(path, []byte(b), 0o644); err != nil {
29
+ t.Fatal(err)
30
+ }
31
+ return path
32
+ }
33
+
34
+ func run(args ...string) (int, string, string) {
35
+ var out, errb bytes.Buffer
36
+ code := Run(args, &out, &errb)
37
+ return code, out.String(), errb.String()
38
+ }
39
+
40
+ func newRun(t *testing.T) string {
41
+ dir := filepath.Join(t.TempDir(), "run")
42
+ write(t, filepath.Join(dir, "CURRENT"), "g0001\n")
43
+ write(t, filepath.Join(dir, "g0001/run.json"), J{"run_id": "run-1",
44
+ "revisions": J{"plan": J{"rev": 1, "file": "revisions/plan-r1.json"}}})
45
+ write(t, filepath.Join(dir, "g0001/dispatch.json"), J{"attempts": L{
46
+ J{"task_id": "T-1", "attempt_id": "A0", "role": "implementer", "lane": "fix", "snapshot": "s1", "status": "admitted"},
47
+ J{"task_id": "T-1", "attempt_id": "A1", "role": "implementer", "lane": "fix", "snapshot": "s1", "status": "running"},
48
+ J{"task_id": "R-1", "attempt_id": "A1", "role": "reviewer", "lane": "security", "snapshot": "s1", "status": "running"},
49
+ }})
50
+ write(t, filepath.Join(dir, "revisions/plan-r1.json"), J{"rev": 1, "tasks": L{
51
+ J{"id": "T-1", "allowed_paths": L{"src/a.py"}}, J{"id": "T-2", "allowed_paths": L{"src/b.py"}}}})
52
+ return dir
53
+ }
54
+
55
+ func writerReceipt() J {
56
+ return J{"schema": "overhaul.receipt/1", "run_id": "run-1", "task_id": "T-1", "attempt_id": "A1",
57
+ "role": "implementer", "lane": "fix", "snapshot": "s1", "started_at": "t0", "finished_at": "t1",
58
+ "outcome": "patch", "changed_paths": L{"src/a.py"}}
59
+ }
60
+
61
+ func TestReceipt(t *testing.T) {
62
+ cases := []struct {
63
+ name string
64
+ mutate func(J)
65
+ observed string // "-" means no --observed flag
66
+ code int
67
+ want string
68
+ }{
69
+ {"writer admissible", func(J) {}, "src/a.py\n\n", 0, `{"verdict": "ADMISSIBLE", "reasons": []}`},
70
+ {"observed differs", func(J) {}, "src/b.py\n", 1, "observed paths ['src/b.py'] differ from claimed ['src/a.py']"},
71
+ {"claimed outside contract", func(r J) { r["changed_paths"] = L{"src/a.py", "src/c.py"} }, "src/a.py\nsrc/c.py\n", 1, "changed paths outside contract: ['src/c.py']"},
72
+ {"writer needs observed", func(J) {}, "-", 1, "writer receipt needs observed changed paths (--observed)"},
73
+ {"snapshot mismatch", func(r J) { r["snapshot"] = "s2" }, "src/a.py\n", 1, "snapshot differs from the dispatch packet"},
74
+ {"bad outcome", func(r J) { r["outcome"] = "done" }, "src/a.py\n", 1, "outcome must be one of ['findings', 'gap', 'no-findings', 'patch', 'verdict']"},
75
+ {"missing finished_at", func(r J) { delete(r, "finished_at") }, "src/a.py\n", 1, "missing finished_at"},
76
+ {"wrong run", func(r J) { r["run_id"] = "run-2" }, "src/a.py\n", 1, "wrong schema or run"},
77
+ {"unknown attempt", func(r J) { r["attempt_id"] = "A9" }, "-", 1, "no such dispatched attempt"},
78
+ {"duplicate or late", func(r J) { r["attempt_id"] = "A0" }, "src/a.py\n", 3, `{"verdict": "DUPLICATE_OR_LATE", "attempt_status": "admitted"}`},
79
+ {"reviewer no-findings without inspected", func(r J) {
80
+ r["task_id"], r["role"], r["lane"], r["outcome"] = "R-1", "reviewer", "security", "no-findings"
81
+ delete(r, "changed_paths")
82
+ }, "-", 1, "no-findings without inspected ranges is malformed"},
83
+ {"reviewer admissible", func(r J) {
84
+ r["task_id"], r["role"], r["lane"], r["outcome"], r["inspected"] = "R-1", "reviewer", "security", "no-findings", L{"src/a.py:1-9"}
85
+ }, "-", 0, "ADMISSIBLE"},
86
+ }
87
+ for _, c := range cases {
88
+ t.Run(c.name, func(t *testing.T) {
89
+ dir := newRun(t)
90
+ rc := writerReceipt()
91
+ c.mutate(rc)
92
+ args := []string{"receipt", dir, write(t, filepath.Join(dir, "receipts/r.json"), rc)}
93
+ if c.observed != "-" {
94
+ args = append(args, "--observed", write(t, filepath.Join(dir, "observed.txt"), c.observed))
95
+ }
96
+ code, out, errs := run(args...)
97
+ if code != c.code || !strings.Contains(out, c.want) {
98
+ t.Fatalf("want %d with %q, got %d:\n%s%s", c.code, c.want, code, out, errs)
99
+ }
100
+ })
101
+ }
102
+ }
103
+
104
+ func TestReceiptIOError(t *testing.T) {
105
+ dir := newRun(t)
106
+ if code, _, errs := run("receipt", dir, filepath.Join(dir, "missing.json")); code != 2 || !strings.HasPrefix(errs, "ERROR: ") {
107
+ t.Fatalf("got %d %s", code, errs)
108
+ }
109
+ if code, _, _ := run("receipt", dir, "x.json", "--observed"); code != 2 {
110
+ t.Fatalf("dangling --observed exit %d", code)
111
+ }
112
+ if code, _, _ := run("anchor"); code != 2 {
113
+ t.Fatalf("usage exit %d", code)
114
+ }
115
+ }
116
+
117
+ func TestAnchor(t *testing.T) {
118
+ loc := func(path string, start, end int, quote string) J {
119
+ return J{"path": path, "start": start, "end": end, "quote": quote}
120
+ }
121
+ cases := []struct {
122
+ name string
123
+ findings L
124
+ code int
125
+ want string
126
+ }{
127
+ {"match", L{J{"id": "F-1", "locations": L{loc("src/a.py", 2, 2, " return request.user ")}}}, 0, `{"verdict": "ANCHORED", "reasons": []}`},
128
+ {"multi-line match", L{J{"id": "F-1", "locations": L{loc("src/a.py", 1, 2, "(request): return request")}}}, 0, "ANCHORED"},
129
+ {"wrong quote", L{J{"id": "F-1", "locations": L{loc("src/a.py", 2, 2, "return request.admin")}}}, 1, "F-1: quote not found at src/a.py:2-2"},
130
+ {"lines outside file", L{J{"id": "F-1", "locations": L{loc("src/a.py", 2, 9, "return request.user")}}}, 1, "F-1: src/a.py lines 2-9 outside file (3 lines)"},
131
+ {"path escape", L{J{"id": "F-1", "locations": L{loc("../secret.py", 1, 1, "top secret value")}}}, 1, "F-1: ../secret.py is not a file under the reviewed tree"},
132
+ {"directory", L{J{"id": "F-1", "locations": L{loc("src", 1, 1, "whatever long")}}}, 1, "is not a file under"},
133
+ {"too short", L{J{"id": "F-1", "locations": L{loc("src/a.py", 2, 2, "return")}}}, 1, "F-1: quote at src/a.py:2 is too short to anchor; quote the whole line"},
134
+ {"short quote equal to window", L{J{"id": "F-1", "locations": L{loc("src/a.py", 3, 3, "x = 1")}}}, 0, "ANCHORED"},
135
+ {"no locations", L{J{"id": "F-2"}}, 1, "F-2: no locations to anchor"},
136
+ }
137
+ for _, c := range cases {
138
+ t.Run(c.name, func(t *testing.T) {
139
+ dir := t.TempDir()
140
+ tree := filepath.Join(dir, "tree")
141
+ write(t, filepath.Join(tree, "src/a.py"), "def handler(request):\n return request.user\nx = 1\n")
142
+ write(t, filepath.Join(dir, "secret.py"), "top secret value\n")
143
+ code, out, errs := run("anchor", tree, write(t, filepath.Join(dir, "p.json"), J{"findings": c.findings}))
144
+ if code != c.code || !strings.Contains(out, c.want) {
145
+ t.Fatalf("want %d with %q, got %d:\n%s%s", c.code, c.want, code, out, errs)
146
+ }
147
+ })
148
+ }
149
+ }
150
+
151
+ func TestAnchorBadInput(t *testing.T) {
152
+ dir := t.TempDir()
153
+ for _, doc := range []any{J{"findings": L{}}, L{}, J{"items": L{J{"id": "F-1"}}}} {
154
+ code, _, errs := run("anchor", dir, write(t, filepath.Join(dir, "p.json"), doc))
155
+ if code != 2 || !strings.Contains(errs, `expected {"findings": [...]} with at least one finding`) {
156
+ t.Fatalf("%v: got %d %s", doc, code, errs)
157
+ }
158
+ }
159
+ }
160
+
161
+ func TestSplitlines(t *testing.T) {
162
+ got := splitlines("a\r\nb\rc\n\nd\u2028e")
163
+ want := []string{"a", "b", "c", "", "d", "e"}
164
+ if strings.Join(got, "|") != strings.Join(want, "|") {
165
+ t.Fatalf("splitlines = %q", got)
166
+ }
167
+ if pyJSON("é\"<\x01") != `"\u00e9\"<\u0001"` {
168
+ t.Fatalf("pyJSON = %s", pyJSON("é\"<\x01"))
169
+ }
170
+ }