devrites 3.2.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/docs/engine/commands.md +7 -5
- package/engine/commands.go +2 -2
- package/engine/hooks.go +17 -3
- package/engine/hooks_workspace.go +18 -3
- package/engine/internal/doctor/doctor.go +21 -23
- package/engine/internal/doctor/doctor_test.go +31 -3
- package/engine/tests/doctor_cli_test.go +15 -0
- package/engine/tests/hook_test.go +93 -0
- package/pack/.claude/skills/rite-doctor/SKILL.md +2 -2
- package/pack/.claude/skills/rite-upgrade/SKILL.md +1 -1
- package/pack/generated/claude/skills/rite-doctor/SKILL.md +2 -2
- package/pack/generated/claude/skills/rite-upgrade/SKILL.md +1 -1
- package/pack/generated/codex/AGENTS.md +4 -3
- package/pack/generated/codex/hooks.json +15 -0
- package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-audit/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-doubt/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-interview/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-lib/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-adopt/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-build/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-clarify/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-customize/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-define/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-doctor/SKILL.md +6 -6
- package/pack/generated/codex/skills/rite-dogfood/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-explain/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-frame/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-handoff/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-learn/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-plan/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-polish/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-pov/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-prototype/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-prove/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-quick/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-resolve/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-review/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-seal/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-ship/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-spec/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-status/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-temper/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-upgrade/SKILL.md +5 -5
- package/pack/generated/codex/skills/rite-vet/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +4 -4
- package/package.json +2 -2
- package/scripts/check-npm-audit.mjs +110 -0
- package/scripts/codex-generate.sh +24 -8
- package/scripts/npm-audit-exceptions.json +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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
|
+
## [3.2.2](https://github.com/ViktorsBaikers/DevRites/compare/v3.2.1...v3.2.2) (2026-07-25)
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
* **agents:** restore guarded codex dispatch ([2d05e20](https://github.com/ViktorsBaikers/DevRites/commit/2d05e20f6196774827d1496275f4c3cb5c5e22e2))
|
|
10
|
+
* **ci:** govern bundled brace expansion advisory ([d1d0786](https://github.com/ViktorsBaikers/DevRites/commit/d1d078667fa42b61d8bec823136c628ca53fc6d8))
|
|
11
|
+
|
|
12
|
+
## [3.2.1](https://github.com/ViktorsBaikers/DevRites/compare/v3.2.0...v3.2.1) (2026-07-24)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
* **ci:** govern temporary npm audit exceptions ([c2219e0](https://github.com/ViktorsBaikers/DevRites/commit/c2219e0ff11f11ee3f2e2ffa7c5d35b480c5eb76))
|
|
17
|
+
* **rite:** repair doctor compatibility ([811f339](https://github.com/ViktorsBaikers/DevRites/commit/811f339d22d3ec079549a5d50838d7596c72c7da))
|
|
18
|
+
|
|
5
19
|
## [3.2.0](https://github.com/ViktorsBaikers/DevRites/compare/v3.1.0...v3.2.0) (2026-07-24)
|
|
6
20
|
|
|
7
21
|
### Added
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ final commit, push, and tag, and it requires a typed `GO` confirmation.
|
|
|
25
25
|
Unattended runs may create local WIP checkpoint commits along the way, but only
|
|
26
26
|
Ship collapses and pushes them.
|
|
27
27
|
|
|
28
|
-
**Status:** [`v3.2.
|
|
28
|
+
**Status:** [`v3.2.2`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v3.2.2): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
|
|
29
29
|
|
|
30
30
|
## Quick start
|
|
31
31
|
|
package/docs/engine/commands.md
CHANGED
|
@@ -77,11 +77,13 @@ hazards: ok
|
|
|
77
77
|
`3`): the binary won't silently mis-parse newer state.
|
|
78
78
|
- Additive schema changes (older state read by a newer binary) are always fine.
|
|
79
79
|
|
|
80
|
-
The pack version
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
The pack version comes from the installer-owned
|
|
81
|
+
`.claude/devrites.manifest`. The old `.claude/devrites.version` marker remains
|
|
82
|
+
readable for compatibility; without either source the pack is reported
|
|
83
|
+
`unknown` and no skew is asserted. The project's own `package.json` is never
|
|
84
|
+
treated as DevRites provenance. Doctor also reports linked-worktree/submodule
|
|
85
|
+
identity, merge/rebase state, host-artifact drift, and project extensions that
|
|
86
|
+
have artifacts but no optional `provenance.json`.
|
|
85
87
|
|
|
86
88
|
### Root safety at command dispatch
|
|
87
89
|
|
package/engine/commands.go
CHANGED
|
@@ -218,8 +218,8 @@ func gitOperation(projectDir, resolvedGitDir string) string {
|
|
|
218
218
|
// versions. It exits nonzero for an unsafe root or newer state schema. Missing
|
|
219
219
|
// optional data and warnings remain diagnostic.
|
|
220
220
|
func cmdDoctor(args []string, stdout, stderr io.Writer) int {
|
|
221
|
-
if len(args)
|
|
222
|
-
fmt.Fprintln(stderr, "usage: devrites-engine doctor")
|
|
221
|
+
if len(args) > 1 || len(args) == 1 && args[0] != "--verbose" {
|
|
222
|
+
fmt.Fprintln(stderr, "usage: devrites-engine doctor [--verbose]")
|
|
223
223
|
return exitUsage
|
|
224
224
|
}
|
|
225
225
|
facts, resolveErr := rootfacts.Resolve(os.Getenv("DEVRITES_ROOT"))
|
package/engine/hooks.go
CHANGED
|
@@ -246,16 +246,21 @@ const (
|
|
|
246
246
|
devritesAgentNone devritesAgentKind = iota
|
|
247
247
|
devritesAgentReadonly
|
|
248
248
|
devritesAgentWright
|
|
249
|
+
devritesAgentGenericWright
|
|
249
250
|
devritesAgentInvalid
|
|
250
251
|
)
|
|
251
252
|
|
|
252
253
|
// devritesAgent identifies the one write-capable leaf without copying the agent
|
|
253
254
|
// roster. DEVRITES_AGENT_RUN supplies identity when a hook payload cannot carry
|
|
254
|
-
// agent_type
|
|
255
|
+
// agent_type; the generated Codex project hook explicitly opts built-in
|
|
256
|
+
// explorer/worker identities into the same policies.
|
|
255
257
|
func devritesAgent(payloadAgent string) devritesAgentKind {
|
|
256
258
|
payloadAgent = strings.TrimSpace(payloadAgent)
|
|
257
259
|
envAgent := strings.TrimSpace(os.Getenv("DEVRITES_ACTIVE_AGENT"))
|
|
258
|
-
|
|
260
|
+
genericCompat := os.Getenv("DEVRITES_CODEX_GENERIC_AGENT_COMPAT") == "1"
|
|
261
|
+
declared := os.Getenv("DEVRITES_AGENT_RUN") == "1" ||
|
|
262
|
+
strings.HasPrefix(payloadAgent, "devrites-") ||
|
|
263
|
+
(genericCompat && (payloadAgent == "explorer" || payloadAgent == "worker"))
|
|
259
264
|
if !declared {
|
|
260
265
|
return devritesAgentNone
|
|
261
266
|
}
|
|
@@ -269,6 +274,10 @@ func devritesAgent(payloadAgent string) devritesAgentKind {
|
|
|
269
274
|
switch {
|
|
270
275
|
case agent == "devrites-slice-wright":
|
|
271
276
|
return devritesAgentWright
|
|
277
|
+
case genericCompat && agent == "worker":
|
|
278
|
+
return devritesAgentGenericWright
|
|
279
|
+
case genericCompat && agent == "explorer":
|
|
280
|
+
return devritesAgentReadonly
|
|
272
281
|
case strings.HasPrefix(agent, "devrites-") && len(agent) > len("devrites-"):
|
|
273
282
|
return devritesAgentReadonly
|
|
274
283
|
default:
|
|
@@ -459,13 +468,18 @@ func hookReviewerReadonly(h harness.Harness, stdin io.Reader, stdout, stderr io.
|
|
|
459
468
|
}
|
|
460
469
|
in := h.ParseGuardInput(bytes.NewReader(data))
|
|
461
470
|
kind := devritesAgent(in.AgentType)
|
|
471
|
+
if kind == devritesAgentNone &&
|
|
472
|
+
os.Getenv("DEVRITES_CODEX_GENERIC_AGENT_COMPAT") == "1" &&
|
|
473
|
+
in.AgentType == "" {
|
|
474
|
+
return exitOK
|
|
475
|
+
}
|
|
462
476
|
|
|
463
477
|
// Do not let a global hook interfere with the main thread or another
|
|
464
478
|
// product's agent when no DevRites run is declared.
|
|
465
479
|
if kind == devritesAgentNone && (in.AgentType != "" || os.Getenv("DEVRITES_REVIEWER_AGENT_REQUIRED") == "1") {
|
|
466
480
|
return exitOK
|
|
467
481
|
}
|
|
468
|
-
if kind == devritesAgentWright {
|
|
482
|
+
if kind == devritesAgentWright || kind == devritesAgentGenericWright {
|
|
469
483
|
if !isAgentDispatchTool(in.ToolName) {
|
|
470
484
|
return exitOK
|
|
471
485
|
}
|
|
@@ -563,9 +563,15 @@ func hookWrightScope(h harness.Harness, stdin io.Reader, stdout, stderr io.Write
|
|
|
563
563
|
}
|
|
564
564
|
in := h.ParseGuardInput(bytes.NewReader(data))
|
|
565
565
|
kind := devritesAgent(in.AgentType)
|
|
566
|
+
if kind == devritesAgentNone &&
|
|
567
|
+
os.Getenv("DEVRITES_CODEX_GENERIC_AGENT_COMPAT") == "1" &&
|
|
568
|
+
in.AgentType == "" {
|
|
569
|
+
return exitOK
|
|
570
|
+
}
|
|
566
571
|
forgeDeclared := forgeWrightDeclared()
|
|
567
|
-
|
|
568
|
-
|
|
572
|
+
genericWright := kind == devritesAgentGenericWright
|
|
573
|
+
active := kind != devritesAgentNone && (!genericWright || forgeDeclared)
|
|
574
|
+
if forgeDeclared && kind != devritesAgentWright && !genericWright {
|
|
569
575
|
return denyWright(h, stdout, stderr, forgeWrightDenyReason, drvreason.HookForgeBindingDenied)
|
|
570
576
|
}
|
|
571
577
|
if kind == devritesAgentNone && (in.AgentType != "" || os.Getenv("DEVRITES_WRIGHT_AGENT_REQUIRED") == "1") {
|
|
@@ -607,6 +613,15 @@ func hookWrightScope(h harness.Harness, stdin io.Reader, stdout, stderr io.Write
|
|
|
607
613
|
}
|
|
608
614
|
return exitOK
|
|
609
615
|
}
|
|
616
|
+
if genericWright && !forgeDeclared {
|
|
617
|
+
if !wsIsFile(filepath.Join(dir, ".reconcile-base")) {
|
|
618
|
+
return exitOK
|
|
619
|
+
}
|
|
620
|
+
if wsIsFile(filepath.Join(dir, ".reconcile-inline")) {
|
|
621
|
+
return denyWright(h, stdout, stderr, wrightForbiddenReason, drvreason.HookWrightForbiddenDenied)
|
|
622
|
+
}
|
|
623
|
+
active = true
|
|
624
|
+
}
|
|
610
625
|
|
|
611
626
|
if wrightForbiddenShellRe.MatchString(in.Command) ||
|
|
612
627
|
(opaqueInterpreterRe.MatchString(in.Command) && !safeReadonlyShellCommand(in.Command)) {
|
|
@@ -617,7 +632,7 @@ func hookWrightScope(h harness.Harness, stdin io.Reader, stdout, stderr io.Write
|
|
|
617
632
|
}
|
|
618
633
|
|
|
619
634
|
projectDir := filepath.Dir(root)
|
|
620
|
-
if !forgeDeclared && kind == devritesAgentWright && insideForgeStaging(projectDir) {
|
|
635
|
+
if !forgeDeclared && (kind == devritesAgentWright || genericWright) && insideForgeStaging(projectDir) {
|
|
621
636
|
return denyOrObserveWright(h, stdout, stderr, dir, true, forgeWrightDenyReason, drvreason.HookForgeBindingDenied, []string{"missing Forge binding"})
|
|
622
637
|
}
|
|
623
638
|
if forgeDeclared {
|
|
@@ -5,13 +5,13 @@ package doctor
|
|
|
5
5
|
|
|
6
6
|
import (
|
|
7
7
|
"crypto/sha256"
|
|
8
|
-
"encoding/json"
|
|
9
8
|
"fmt"
|
|
10
9
|
"io/fs"
|
|
11
10
|
"os"
|
|
12
11
|
"path/filepath"
|
|
13
12
|
"strings"
|
|
14
13
|
|
|
14
|
+
"github.com/devrites/devrites/internal/devritespaths"
|
|
15
15
|
"github.com/devrites/devrites/internal/rootfacts"
|
|
16
16
|
"github.com/devrites/devrites/internal/state"
|
|
17
17
|
"github.com/devrites/devrites/internal/version"
|
|
@@ -160,20 +160,32 @@ func (r *Report) Render() string {
|
|
|
160
160
|
return b.String()
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
// packVersion discovers the installed DevRites pack version
|
|
164
|
-
//
|
|
165
|
-
// package.json at the project root; either absent leaves the pack Unknown so no
|
|
166
|
-
// false skew is asserted.
|
|
163
|
+
// packVersion discovers the installed DevRites pack version from the install
|
|
164
|
+
// manifest. The old standalone marker remains readable for compatibility.
|
|
167
165
|
func packVersion(projectDir string) string {
|
|
168
|
-
if v :=
|
|
166
|
+
if v := manifestVersion(filepath.Join(projectDir, filepath.FromSlash(devritespaths.ManifestName))); v != "" {
|
|
169
167
|
return v
|
|
170
168
|
}
|
|
171
|
-
if v :=
|
|
169
|
+
if v := firstLine(filepath.Join(projectDir, ".claude", "devrites.version")); v != "" {
|
|
172
170
|
return v
|
|
173
171
|
}
|
|
174
172
|
return Unknown
|
|
175
173
|
}
|
|
176
174
|
|
|
175
|
+
func manifestVersion(path string) string {
|
|
176
|
+
raw, err := os.ReadFile(path)
|
|
177
|
+
if err != nil {
|
|
178
|
+
return ""
|
|
179
|
+
}
|
|
180
|
+
const prefix = "# devrites-version:"
|
|
181
|
+
for _, line := range strings.Split(string(raw), "\n") {
|
|
182
|
+
if strings.HasPrefix(line, prefix) {
|
|
183
|
+
return strings.TrimSpace(strings.TrimPrefix(line, prefix))
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return ""
|
|
187
|
+
}
|
|
188
|
+
|
|
177
189
|
func firstLine(path string) string {
|
|
178
190
|
raw, err := os.ReadFile(path)
|
|
179
191
|
if err != nil {
|
|
@@ -200,8 +212,8 @@ func driftChecks(projectDir string) []string {
|
|
|
200
212
|
if isDir(filepath.Join(projectDir, ".claude", "agents")) && !isDir(filepath.Join(projectDir, ".codex", "agents")) {
|
|
201
213
|
checks = append(checks, "WARN: Claude agents installed but Codex agent mirror .codex/agents is missing")
|
|
202
214
|
}
|
|
203
|
-
if
|
|
204
|
-
checks = append(checks, "WARN: installed skills have no .claude/devrites.
|
|
215
|
+
if packVersion(projectDir) == Unknown && isDir(filepath.Join(projectDir, ".claude", "skills")) {
|
|
216
|
+
checks = append(checks, "WARN: installed skills have no versioned .claude/devrites.manifest provenance")
|
|
205
217
|
}
|
|
206
218
|
if generatedClaudeDrift(projectDir) {
|
|
207
219
|
checks = append(checks, "[DRV-GENERATED-DRIFT] WARN: pack/generated/claude differs from canonical pack/.claude; run `bash scripts/build-host-artifacts.sh`")
|
|
@@ -292,17 +304,3 @@ func isDir(path string) bool {
|
|
|
292
304
|
info, err := os.Stat(path)
|
|
293
305
|
return err == nil && info.IsDir()
|
|
294
306
|
}
|
|
295
|
-
|
|
296
|
-
func packageJSONVersion(path string) string {
|
|
297
|
-
raw, err := os.ReadFile(path)
|
|
298
|
-
if err != nil {
|
|
299
|
-
return ""
|
|
300
|
-
}
|
|
301
|
-
var pkg struct {
|
|
302
|
-
Version string `json:"version"`
|
|
303
|
-
}
|
|
304
|
-
if err := json.Unmarshal(raw, &pkg); err != nil {
|
|
305
|
-
return ""
|
|
306
|
-
}
|
|
307
|
-
return strings.TrimSpace(pkg.Version)
|
|
308
|
-
}
|
|
@@ -64,12 +64,13 @@ func TestDiagnoseOKWhenAligned(t *testing.T) {
|
|
|
64
64
|
func TestDiagnoseWarnsWhenBinaryOlderThanPack(t *testing.T) {
|
|
65
65
|
withBinaryVersion(t, "1.0.0")
|
|
66
66
|
projectDir := t.TempDir()
|
|
67
|
-
//
|
|
67
|
+
// Installed pack newer than the binary → warn, not refuse.
|
|
68
68
|
claude := filepath.Join(projectDir, ".claude")
|
|
69
69
|
if err := os.MkdirAll(claude, 0o755); err != nil {
|
|
70
70
|
t.Fatal(err)
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
manifest := "# devrites-version: 2.0.0\n.claude/skills/rite/SKILL.md\tabc\n"
|
|
73
|
+
if err := os.WriteFile(filepath.Join(claude, "devrites.manifest"), []byte(manifest), 0o644); err != nil {
|
|
73
74
|
t.Fatal(err)
|
|
74
75
|
}
|
|
75
76
|
root := t.TempDir()
|
|
@@ -90,6 +91,33 @@ func TestDiagnoseWarnsWhenBinaryOlderThanPack(t *testing.T) {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
func TestPackVersionUsesManifestBeforeLegacyMarker(t *testing.T) {
|
|
95
|
+
projectDir := t.TempDir()
|
|
96
|
+
claude := filepath.Join(projectDir, ".claude")
|
|
97
|
+
if err := os.MkdirAll(claude, 0o755); err != nil {
|
|
98
|
+
t.Fatal(err)
|
|
99
|
+
}
|
|
100
|
+
if err := os.WriteFile(filepath.Join(claude, "devrites.manifest"), []byte("# devrites-version: 3.2.0\n"), 0o644); err != nil {
|
|
101
|
+
t.Fatal(err)
|
|
102
|
+
}
|
|
103
|
+
if err := os.WriteFile(filepath.Join(claude, "devrites.version"), []byte("2.9.0\n"), 0o644); err != nil {
|
|
104
|
+
t.Fatal(err)
|
|
105
|
+
}
|
|
106
|
+
if got := packVersion(projectDir); got != "3.2.0" {
|
|
107
|
+
t.Fatalf("packVersion() = %q, want installer manifest version 3.2.0", got)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
func TestPackVersionIgnoresProjectPackageJSON(t *testing.T) {
|
|
112
|
+
projectDir := t.TempDir()
|
|
113
|
+
if err := os.WriteFile(filepath.Join(projectDir, "package.json"), []byte(`{"version":"99.0.0"}`), 0o644); err != nil {
|
|
114
|
+
t.Fatal(err)
|
|
115
|
+
}
|
|
116
|
+
if got := packVersion(projectDir); got != Unknown {
|
|
117
|
+
t.Fatalf("packVersion() = %q, want %q without DevRites install provenance", got, Unknown)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
func TestDiagnoseRefusesNewerStateSchema(t *testing.T) {
|
|
94
122
|
withBinaryVersion(t, "2.6.1")
|
|
95
123
|
root := t.TempDir()
|
|
@@ -143,7 +171,7 @@ func TestDiagnoseReportsHostArtifactDrift(t *testing.T) {
|
|
|
143
171
|
t.Fatal("expected drift checks")
|
|
144
172
|
}
|
|
145
173
|
out := r.Render()
|
|
146
|
-
for _, want := range []string{"devrites-engine hooks", "Codex skill mirror", "devrites.
|
|
174
|
+
for _, want := range []string{"devrites-engine hooks", "Codex skill mirror", "devrites.manifest provenance"} {
|
|
147
175
|
if !strings.Contains(out, want) {
|
|
148
176
|
t.Fatalf("doctor output missing %q:\n%s", want, out)
|
|
149
177
|
}
|
|
@@ -25,6 +25,21 @@ func TestDoctorPrintsTriangle(t *testing.T) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
func TestDoctorVerboseMatchesPlainDoctor(t *testing.T) {
|
|
29
|
+
root := newWorkspace(t)
|
|
30
|
+
plainOut, plainErr, plainCode := runDevrites(t, root, "doctor")
|
|
31
|
+
verboseOut, verboseErr, verboseCode := runDevrites(t, root, "doctor", "--verbose")
|
|
32
|
+
if plainCode != 0 || verboseCode != 0 {
|
|
33
|
+
t.Fatalf("doctor exits: plain=%d verbose=%d", plainCode, verboseCode)
|
|
34
|
+
}
|
|
35
|
+
if verboseCode != plainCode || verboseOut != plainOut || verboseErr != plainErr {
|
|
36
|
+
t.Fatalf(
|
|
37
|
+
"doctor --verbose differs from doctor:\nplain: code=%d stdout=%q stderr=%q\nverbose: code=%d stdout=%q stderr=%q",
|
|
38
|
+
plainCode, plainOut, plainErr, verboseCode, verboseOut, verboseErr,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
func TestDoctorReportsStaleActiveWithPasteableRepair(t *testing.T) {
|
|
29
44
|
root := newWorkspace(t)
|
|
30
45
|
if err := os.WriteFile(filepath.Join(root, "ACTIVE"), []byte("ghost\n"), 0o644); err != nil {
|
|
@@ -347,6 +347,36 @@ func TestHookReviewerReadonlyAgentRequiredSkipsNonDevrites(t *testing.T) {
|
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
func TestHookReviewerReadonlyBindsCodexExplorerCompatibility(t *testing.T) {
|
|
351
|
+
root := newWorkspace(t)
|
|
352
|
+
in := `{"tool_name":"Edit","tool_input":{"file_path":"src/app.go"},"agent_type":"explorer"}`
|
|
353
|
+
out, errOut, code := runDevritesIO(t, root, in,
|
|
354
|
+
[]string{"DEVRITES_CODEX_GENERIC_AGENT_COMPAT=1"},
|
|
355
|
+
"hook", "reviewer-readonly", "--harness=codex")
|
|
356
|
+
if code != 0 {
|
|
357
|
+
t.Fatalf("exit=%d stderr=%q", code, errOut)
|
|
358
|
+
}
|
|
359
|
+
if decision, _ := parsePermissionDecision(t, out); decision != "deny" {
|
|
360
|
+
t.Fatalf("generic explorer mutation was not denied: %q", out)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
func TestCodexGenericCompatibilityDoesNotShadowRoot(t *testing.T) {
|
|
365
|
+
root := newWorkspace(t)
|
|
366
|
+
in := `{"tool_name":"Edit","tool_input":{"file_path":"src/app.go"}}`
|
|
367
|
+
env := []string{
|
|
368
|
+
"DEVRITES_CODEX_GENERIC_AGENT_COMPAT=1",
|
|
369
|
+
"DEVRITES_HOOK_PROFILE=strict",
|
|
370
|
+
}
|
|
371
|
+
for _, guard := range []string{"reviewer-readonly", "wright-scope"} {
|
|
372
|
+
out, errOut, code := runDevritesIO(t, root, in, env,
|
|
373
|
+
"hook", guard, "--harness=codex")
|
|
374
|
+
if code != 0 || strings.TrimSpace(out) != "" {
|
|
375
|
+
t.Fatalf("%s shadowed root: exit=%d out=%q stderr=%q", guard, code, out, errOut)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
350
380
|
func TestHookReviewerReadonlyActiveLeafDeniesEveryMutationSurface(t *testing.T) {
|
|
351
381
|
root := newWorkspace(t)
|
|
352
382
|
env := []string{
|
|
@@ -435,6 +465,53 @@ func TestHookWrightScopeUsesExactOrchestratorAllowlist(t *testing.T) {
|
|
|
435
465
|
}
|
|
436
466
|
}
|
|
437
467
|
|
|
468
|
+
func TestHookWrightScopeBindsCodexWorkerDuringReconcileWindow(t *testing.T) {
|
|
469
|
+
root := newWorkspace(t)
|
|
470
|
+
writeActive(t, root, "auth-tokens")
|
|
471
|
+
workspace := filepath.Join(root, "features", "auth-tokens")
|
|
472
|
+
if err := os.WriteFile(filepath.Join(workspace, ".wright-allowlist"), []byte("src/app.go\n"), 0o600); err != nil {
|
|
473
|
+
t.Fatal(err)
|
|
474
|
+
}
|
|
475
|
+
if err := os.WriteFile(filepath.Join(workspace, ".reconcile-base"), []byte("snapshot\n"), 0o600); err != nil {
|
|
476
|
+
t.Fatal(err)
|
|
477
|
+
}
|
|
478
|
+
env := []string{"DEVRITES_CODEX_GENERIC_AGENT_COMPAT=1"}
|
|
479
|
+
|
|
480
|
+
for _, test := range []struct {
|
|
481
|
+
name string
|
|
482
|
+
input string
|
|
483
|
+
deny bool
|
|
484
|
+
}{
|
|
485
|
+
{"listed edit", `{"tool_name":"Edit","tool_input":{"file_path":"src/app.go"},"agent_type":"worker"}`, false},
|
|
486
|
+
{"unlisted edit", `{"tool_name":"Edit","tool_input":{"file_path":"src/other.go"},"agent_type":"worker"}`, true},
|
|
487
|
+
} {
|
|
488
|
+
t.Run(test.name, func(t *testing.T) {
|
|
489
|
+
out, errOut, code := runDevritesIO(t, root, test.input, env,
|
|
490
|
+
"hook", "wright-scope", "--harness=codex")
|
|
491
|
+
if code != 0 {
|
|
492
|
+
t.Fatalf("exit=%d stderr=%q", code, errOut)
|
|
493
|
+
}
|
|
494
|
+
if test.deny {
|
|
495
|
+
if decision, _ := parsePermissionDecision(t, out); decision != "deny" {
|
|
496
|
+
t.Fatalf("expected deny, got %q", out)
|
|
497
|
+
}
|
|
498
|
+
} else if strings.TrimSpace(out) != "" {
|
|
499
|
+
t.Fatalf("expected allow, got %q", out)
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if err := os.Remove(filepath.Join(workspace, ".reconcile-base")); err != nil {
|
|
505
|
+
t.Fatal(err)
|
|
506
|
+
}
|
|
507
|
+
out, _, code := runDevritesIO(t, root,
|
|
508
|
+
`{"tool_name":"Edit","tool_input":{"file_path":"src/other.go"},"agent_type":"worker"}`,
|
|
509
|
+
env, "hook", "wright-scope", "--harness=codex")
|
|
510
|
+
if code != 0 || strings.TrimSpace(out) != "" {
|
|
511
|
+
t.Fatalf("generic worker outside a reconcile window was shadowed: exit=%d out=%q", code, out)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
438
515
|
func TestHookWrightScopeFailsClosedWhenWorkspaceResolutionFails(t *testing.T) {
|
|
439
516
|
root := newWorkspace(t)
|
|
440
517
|
if err := os.Remove(filepath.Join(root, "ACTIVE")); err != nil && !os.IsNotExist(err) {
|
|
@@ -523,6 +600,22 @@ func TestHookWrightScopeBindsForgeCandidateManifestIdentity(t *testing.T) {
|
|
|
523
600
|
if code != 0 || strings.TrimSpace(out) != "" {
|
|
524
601
|
t.Fatalf("valid Forge candidate was denied: exit=%d stdout=%s stderr=%s", code, out, stderr)
|
|
525
602
|
}
|
|
603
|
+
genericEnv := append([]string{"DEVRITES_CODEX_GENERIC_AGENT_COMPAT=1"}, baseEnv[2:]...)
|
|
604
|
+
genericInput := `{"tool_name":"Edit","agent_id":"worker-a","agent_type":"worker","tool_input":{"file_path":"tracked.txt"}}`
|
|
605
|
+
out, stderr, code = runDevritesAt(t, repo, candidate.Worktree, genericInput, genericEnv,
|
|
606
|
+
"hook", "wright-scope", "--harness=codex")
|
|
607
|
+
if code != 0 || strings.TrimSpace(out) != "" {
|
|
608
|
+
t.Fatalf("valid generic Forge worker was denied: exit=%d stdout=%s stderr=%s", code, out, stderr)
|
|
609
|
+
}
|
|
610
|
+
out, _, code = runDevritesAt(t, repo, candidate.Worktree,
|
|
611
|
+
`{"tool_name":"Edit","agent_id":"worker-a","agent_type":"worker","tool_input":{"file_path":"outside.txt"}}`,
|
|
612
|
+
genericEnv, "hook", "wright-scope", "--harness=codex")
|
|
613
|
+
if code != 0 {
|
|
614
|
+
t.Fatalf("generic Forge worker hook exit=%d", code)
|
|
615
|
+
}
|
|
616
|
+
if decision, _ := parsePermissionDecision(t, out); decision != "deny" {
|
|
617
|
+
t.Fatalf("generic Forge worker escaped its allowlist: %s", out)
|
|
618
|
+
}
|
|
526
619
|
|
|
527
620
|
replaceEnv := func(name, value string) []string {
|
|
528
621
|
env := append([]string(nil), baseEnv...)
|
|
@@ -19,7 +19,7 @@ It is **read-only**: it never edits the workspace, never advances a phase, never
|
|
|
19
19
|
## Workflow
|
|
20
20
|
1. Run the diagnose core verbosely (resolve across install layouts):
|
|
21
21
|
```bash
|
|
22
|
-
devrites-engine doctor
|
|
22
|
+
devrites-engine doctor; echo "doctor rc=$?"
|
|
23
23
|
```
|
|
24
24
|
1a. **Surface the learnings nudge**: point the user at `/rite-learn` when a pattern recurs across
|
|
25
25
|
shipped features (read-only; silent when there's nothing to say):
|
|
@@ -68,7 +68,7 @@ progress`, but it follows the compact labels and one-next-action rule from
|
|
|
68
68
|
```
|
|
69
69
|
Done: DevRites health checked; <OK | n issues>.
|
|
70
70
|
Changed: workspace only
|
|
71
|
-
Evidence: devrites-engine doctor
|
|
71
|
+
Evidence: devrites-engine doctor rc=<0|1>; health <skipped|PASS|WARN|FAIL>; reindex <skipped|result>; learnings nudge <summary|none>; extensions/overrides <ok|n issues>
|
|
72
72
|
Open: <none | issue count and top issue>
|
|
73
73
|
Next: <single command for the most urgent issue>
|
|
74
74
|
Record: not applicable
|
|
@@ -42,7 +42,7 @@ Plan, Vet, Converge, and Build phase contracts needed by the assessment.
|
|
|
42
42
|
|
|
43
43
|
0. **Orient and normalize structure.** Read the active slug or `$ARGUMENTS`. Run:
|
|
44
44
|
```bash
|
|
45
|
-
devrites-engine doctor
|
|
45
|
+
devrites-engine doctor; echo "doctor rc=$?"
|
|
46
46
|
```
|
|
47
47
|
A binary/pack integrity mismatch stops at `/rite-doctor`; do not run migration or
|
|
48
48
|
mutate semantic artifacts. Once healthy, orient without writing:
|
|
@@ -19,7 +19,7 @@ It is **read-only**: it never edits the workspace, never advances a phase, never
|
|
|
19
19
|
## Workflow
|
|
20
20
|
1. Run the diagnose core verbosely (resolve across install layouts):
|
|
21
21
|
```bash
|
|
22
|
-
devrites-engine doctor
|
|
22
|
+
devrites-engine doctor; echo "doctor rc=$?"
|
|
23
23
|
```
|
|
24
24
|
1a. **Surface the learnings nudge**: point the user at `/rite-learn` when a pattern recurs across
|
|
25
25
|
shipped features (read-only; silent when there's nothing to say):
|
|
@@ -68,7 +68,7 @@ progress`, but it follows the compact labels and one-next-action rule from
|
|
|
68
68
|
```
|
|
69
69
|
Done: DevRites health checked; <OK | n issues>.
|
|
70
70
|
Changed: workspace only
|
|
71
|
-
Evidence: devrites-engine doctor
|
|
71
|
+
Evidence: devrites-engine doctor rc=<0|1>; health <skipped|PASS|WARN|FAIL>; reindex <skipped|result>; learnings nudge <summary|none>; extensions/overrides <ok|n issues>
|
|
72
72
|
Open: <none | issue count and top issue>
|
|
73
73
|
Next: <single command for the most urgent issue>
|
|
74
74
|
Record: not applicable
|
|
@@ -42,7 +42,7 @@ Plan, Vet, Converge, and Build phase contracts needed by the assessment.
|
|
|
42
42
|
|
|
43
43
|
0. **Orient and normalize structure.** Read the active slug or `$ARGUMENTS`. Run:
|
|
44
44
|
```bash
|
|
45
|
-
devrites-engine doctor
|
|
45
|
+
devrites-engine doctor; echo "doctor rc=$?"
|
|
46
46
|
```
|
|
47
47
|
A binary/pack integrity mismatch stops at `/rite-doctor`; do not run migration or
|
|
48
48
|
mutate semantic artifacts. Once healthy, orient without writing:
|
|
@@ -13,9 +13,10 @@ This project has DevRites installed for both Claude Code and Codex.
|
|
|
13
13
|
- Before using any DevRites workflow skill, read `.agents/skills/devrites-lib/reference/standards/core.md`. Load other `.agents/skills/devrites-lib/reference/standards/*.md` files when the skill or rule index asks for them. These are DevRites engineering standards, not Codex exec-policy `.rules` files.
|
|
14
14
|
- Custom Codex subagents generated from the DevRites review agents live in `.codex/agents`.
|
|
15
15
|
- In DevRites guidance, **invoke** means run a skill inline in the current context; **dispatch** means start a fresh agent with `spawn_agent`, wait for it, and reconcile its result.
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
16
|
+
- Inspect the current `spawn_agent` role list. Use an exposed named `devrites-<role>` with `fork_turns="none"`; full-history forks inherit the parent type.
|
|
17
|
+
- If a named role is not exposed, use generic `explorer` for read-only work with `fork_turns="none"` and tell it to read `.codex/agents/devrites-<role>.toml`. Trusted `.codex/hooks.json` binds `agent_type=explorer` to the fail-closed reviewer guard.
|
|
18
|
+
- For `devrites-slice-wright`, trusted `.codex/hooks.json` binds generic `worker` (`agent_type=worker`) to the active reconcile window and exact `.wright-allowlist`. Dispatch it with `fork_turns="none"`, the unchanged packet, and `.codex/agents/devrites-slice-wright.toml`.
|
|
19
|
+
- A missing custom role is not evidence that spawning is unavailable. Use the labelled inline `.reconcile-inline` path only when project hooks are unavailable or untrusted, spawning is unavailable, or higher-priority policy rejects the safe rung.
|
|
19
20
|
- Claude Code agent hook metadata is not active in Codex. The generated Codex agents preserve read-only intent with Codex sandbox settings where possible; still follow DevRites' scope and no-mutation rules explicitly.
|
|
20
21
|
|
|
21
22
|
## Workflow contract
|
|
@@ -25,6 +25,21 @@
|
|
|
25
25
|
"statusMessage": "DevRites: checking build write boundary"
|
|
26
26
|
}
|
|
27
27
|
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"matcher": "Bash|Shell|sh|exec_command|run_command|Edit|Write|MultiEdit|NotebookEdit|apply_patch|exec|js|python|computer|computer_use|write_stdin|run_code|Agent|Task|spawn_agent|delegate|dispatch_agent|create_agent",
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "payload=\"$(cat)\"; if ! command -v devrites-engine >/dev/null 2>&1; then if printf '%s' \"$payload\" | grep -Eq '\"agent_type\"[[:space:]]*:[[:space:]]*\"(explorer|worker)\"'; then printf '%s\\n' 'DevRites: generic Codex leaf guard unavailable; install or repair devrites-engine. (devrites-codex-generic-guard)' >&2; exit 2; fi; exit 0; fi; cd \"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\" 2>/dev/null || { printf '%s\\n' 'DevRites: cannot resolve the project root for a generic Codex leaf. (devrites-codex-generic-guard)' >&2; exit 2; }; printf '%s' \"$payload\" | DEVRITES_CODEX_GENERIC_AGENT_COMPAT=1 devrites-engine hook reviewer-readonly --harness=codex; rc=$?; case \"$rc\" in 0) exit 0 ;; 2) exit 2 ;; *) printf '%s\\n' 'DevRites: generic Codex reviewer guard unavailable or crashed; install or repair devrites-engine. (devrites-codex-generic-guard)' >&2; exit 2 ;; esac",
|
|
35
|
+
"statusMessage": "DevRites: checking generic reviewer boundary"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"type": "command",
|
|
39
|
+
"command": "payload=\"$(cat)\"; if ! command -v devrites-engine >/dev/null 2>&1; then if printf '%s' \"$payload\" | grep -Eq '\"agent_type\"[[:space:]]*:[[:space:]]*\"(explorer|worker)\"'; then printf '%s\\n' 'DevRites: generic Codex leaf guard unavailable; install or repair devrites-engine. (devrites-codex-generic-guard)' >&2; exit 2; fi; exit 0; fi; cd \"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\" 2>/dev/null || { printf '%s\\n' 'DevRites: cannot resolve the project root for a generic Codex leaf. (devrites-codex-generic-guard)' >&2; exit 2; }; printf '%s' \"$payload\" | DEVRITES_CODEX_GENERIC_AGENT_COMPAT=1 devrites-engine hook wright-scope --harness=codex; rc=$?; case \"$rc\" in 0) exit 0 ;; 2) exit 2 ;; *) printf '%s\\n' 'DevRites: generic Codex wright guard unavailable or crashed; install or repair devrites-engine. (devrites-codex-generic-guard)' >&2; exit 2 ;; esac",
|
|
40
|
+
"statusMessage": "DevRites: checking generic wright boundary"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
28
43
|
}
|
|
29
44
|
],
|
|
30
45
|
"PostToolUse": [
|
|
@@ -11,10 +11,10 @@ This is the Codex mirror of a DevRites skill. In Codex:
|
|
|
11
11
|
- Load DevRites engineering standards from `.agents/skills/devrites-lib/reference/standards/`. Read `.agents/skills/devrites-lib/reference/standards/core.md` before workflow work, then load the other `.agents/skills/devrites-lib/reference/standards/*.md` files exactly when this skill asks for them.
|
|
12
12
|
- Use the installed `devrites-engine` binary as the canonical runtime helper surface for orientation, gates, and state mutation.
|
|
13
13
|
- **Invocation and dispatch are different:** invoke means run a skill in this context; dispatch means start a fresh agent with `spawn_agent`, await it, and reconcile its result. Never describe inline skill work as a dispatch.
|
|
14
|
-
-
|
|
15
|
-
- If
|
|
16
|
-
-
|
|
17
|
-
-
|
|
14
|
+
- Inspect the current `spawn_agent` role list. When the named `devrites-<role>` is exposed, dispatch it with `fork_turns="none"`; full-history forks inherit the parent type. The matching project contract is `.codex/agents/devrites-<role>.toml`.
|
|
15
|
+
- If a named role is not exposed, use generic `explorer` for every read-only role with `fork_turns="none"`. Tell it to read `.codex/agents/devrites-<role>.toml`, follow its `developer_instructions`, and execute the unchanged packet. Trusted `.codex/hooks.json` binds `agent_type=explorer` to the fail-closed reviewer read-only guard.
|
|
16
|
+
- For `devrites-slice-wright`, trusted `.codex/hooks.json` binds generic `worker` (`agent_type=worker`) to the active reconcile window and exact `.wright-allowlist`. Dispatch that worker with `fork_turns="none"`, tell it to read `.codex/agents/devrites-slice-wright.toml`, and execute the unchanged packet. Never create `.reconcile-inline` when this safe rung is available.
|
|
17
|
+
- A missing custom role is not evidence that spawning is unavailable. Only when the project hooks are unavailable or untrusted, no spawn primitive exists, or higher-priority policy rejects a safe spawn may the root run the documented discipline inline. Label it `independence: fallback`, never call it independent, create `.reconcile-inline` only for that path, and apply every fallback risk gate.
|
|
18
18
|
- Wait for every required fresh-context dispatch before reconciling or advancing. A backgrounded or lost result is incomplete.
|
|
19
19
|
- Codex project hooks are installed in `.codex/hooks.json`; declared-leaf hooks are scoped inside `.codex/agents/devrites-*.toml`. Review and trust them with `/hooks` before relying on hook enforcement.
|
|
20
20
|
- When this skill asks a HITL question via `AskUserQuestion`: Codex's equivalent (`request_user_input`) exists only in Plan mode. Outside Plan mode, render the option set as a plain numbered list in chat and **end the turn** so the human answers: NEVER silently pick an option yourself; auto-picking is AFK's contract, gated by the `.devrites/AFK` sentinel.
|
|
@@ -12,10 +12,10 @@ This is the Codex mirror of a DevRites skill. In Codex:
|
|
|
12
12
|
- Load DevRites engineering standards from `.agents/skills/devrites-lib/reference/standards/`. Read `.agents/skills/devrites-lib/reference/standards/core.md` before workflow work, then load the other `.agents/skills/devrites-lib/reference/standards/*.md` files exactly when this skill asks for them.
|
|
13
13
|
- Use the installed `devrites-engine` binary as the canonical runtime helper surface for orientation, gates, and state mutation.
|
|
14
14
|
- **Invocation and dispatch are different:** invoke means run a skill in this context; dispatch means start a fresh agent with `spawn_agent`, await it, and reconcile its result. Never describe inline skill work as a dispatch.
|
|
15
|
-
-
|
|
16
|
-
- If
|
|
17
|
-
-
|
|
18
|
-
-
|
|
15
|
+
- Inspect the current `spawn_agent` role list. When the named `devrites-<role>` is exposed, dispatch it with `fork_turns="none"`; full-history forks inherit the parent type. The matching project contract is `.codex/agents/devrites-<role>.toml`.
|
|
16
|
+
- If a named role is not exposed, use generic `explorer` for every read-only role with `fork_turns="none"`. Tell it to read `.codex/agents/devrites-<role>.toml`, follow its `developer_instructions`, and execute the unchanged packet. Trusted `.codex/hooks.json` binds `agent_type=explorer` to the fail-closed reviewer read-only guard.
|
|
17
|
+
- For `devrites-slice-wright`, trusted `.codex/hooks.json` binds generic `worker` (`agent_type=worker`) to the active reconcile window and exact `.wright-allowlist`. Dispatch that worker with `fork_turns="none"`, tell it to read `.codex/agents/devrites-slice-wright.toml`, and execute the unchanged packet. Never create `.reconcile-inline` when this safe rung is available.
|
|
18
|
+
- A missing custom role is not evidence that spawning is unavailable. Only when the project hooks are unavailable or untrusted, no spawn primitive exists, or higher-priority policy rejects a safe spawn may the root run the documented discipline inline. Label it `independence: fallback`, never call it independent, create `.reconcile-inline` only for that path, and apply every fallback risk gate.
|
|
19
19
|
- Wait for every required fresh-context dispatch before reconciling or advancing. A backgrounded or lost result is incomplete.
|
|
20
20
|
- Codex project hooks are installed in `.codex/hooks.json`; declared-leaf hooks are scoped inside `.codex/agents/devrites-*.toml`. Review and trust them with `/hooks` before relying on hook enforcement.
|
|
21
21
|
- When this skill asks a HITL question via `AskUserQuestion`: Codex's equivalent (`request_user_input`) exists only in Plan mode. Outside Plan mode, render the option set as a plain numbered list in chat and **end the turn** so the human answers: NEVER silently pick an option yourself; auto-picking is AFK's contract, gated by the `.devrites/AFK` sentinel.
|
|
@@ -11,10 +11,10 @@ This is the Codex mirror of a DevRites skill. In Codex:
|
|
|
11
11
|
- Load DevRites engineering standards from `.agents/skills/devrites-lib/reference/standards/`. Read `.agents/skills/devrites-lib/reference/standards/core.md` before workflow work, then load the other `.agents/skills/devrites-lib/reference/standards/*.md` files exactly when this skill asks for them.
|
|
12
12
|
- Use the installed `devrites-engine` binary as the canonical runtime helper surface for orientation, gates, and state mutation.
|
|
13
13
|
- **Invocation and dispatch are different:** invoke means run a skill in this context; dispatch means start a fresh agent with `spawn_agent`, await it, and reconcile its result. Never describe inline skill work as a dispatch.
|
|
14
|
-
-
|
|
15
|
-
- If
|
|
16
|
-
-
|
|
17
|
-
-
|
|
14
|
+
- Inspect the current `spawn_agent` role list. When the named `devrites-<role>` is exposed, dispatch it with `fork_turns="none"`; full-history forks inherit the parent type. The matching project contract is `.codex/agents/devrites-<role>.toml`.
|
|
15
|
+
- If a named role is not exposed, use generic `explorer` for every read-only role with `fork_turns="none"`. Tell it to read `.codex/agents/devrites-<role>.toml`, follow its `developer_instructions`, and execute the unchanged packet. Trusted `.codex/hooks.json` binds `agent_type=explorer` to the fail-closed reviewer read-only guard.
|
|
16
|
+
- For `devrites-slice-wright`, trusted `.codex/hooks.json` binds generic `worker` (`agent_type=worker`) to the active reconcile window and exact `.wright-allowlist`. Dispatch that worker with `fork_turns="none"`, tell it to read `.codex/agents/devrites-slice-wright.toml`, and execute the unchanged packet. Never create `.reconcile-inline` when this safe rung is available.
|
|
17
|
+
- A missing custom role is not evidence that spawning is unavailable. Only when the project hooks are unavailable or untrusted, no spawn primitive exists, or higher-priority policy rejects a safe spawn may the root run the documented discipline inline. Label it `independence: fallback`, never call it independent, create `.reconcile-inline` only for that path, and apply every fallback risk gate.
|
|
18
18
|
- Wait for every required fresh-context dispatch before reconciling or advancing. A backgrounded or lost result is incomplete.
|
|
19
19
|
- Codex project hooks are installed in `.codex/hooks.json`; declared-leaf hooks are scoped inside `.codex/agents/devrites-*.toml`. Review and trust them with `/hooks` before relying on hook enforcement.
|
|
20
20
|
- When this skill asks a HITL question via `AskUserQuestion`: Codex's equivalent (`request_user_input`) exists only in Plan mode. Outside Plan mode, render the option set as a plain numbered list in chat and **end the turn** so the human answers: NEVER silently pick an option yourself; auto-picking is AFK's contract, gated by the `.devrites/AFK` sentinel.
|