devrites 3.2.15 → 3.2.17
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/engine/hooks.go +20 -4
- package/engine/hooks_agent_dispatch.go +297 -48
- package/engine/hooks_events_test.go +40 -0
- package/engine/hooks_workspace.go +26 -6
- package/engine/internal/devritespaths/paths.go +80 -0
- package/engine/internal/lib/adoption.go +22 -7
- package/engine/internal/lib/adoption_test.go +30 -0
- package/engine/internal/lib/closeout.go +34 -11
- package/engine/internal/lib/closeout_test.go +99 -0
- package/engine/internal/lib/dispatchwaive.go +31 -0
- package/engine/internal/lib/dispatchwaive_test.go +23 -0
- package/engine/internal/lib/mutationgate.go +6 -2
- package/engine/internal/lib/reconcile.go +201 -64
- package/engine/internal/lib/reconcile_test.go +159 -12
- package/engine/internal/lib/testintegrity.go +4 -4
- package/engine/internal/lib/testintegrity_test.go +15 -0
- package/engine/internal/lib/util.go +128 -23
- package/engine/main.go +4 -1
- package/engine/testdata/golden/TestParityReconcile/close-window.golden +1 -0
- package/engine/testdata/golden/TestParityReconcile/not-git.golden +1 -1
- package/engine/testdata/golden/TestParityTestIntegrity/not-git.golden +1 -1
- package/engine/tests/hook_test.go +388 -17
- package/engine/tests/parity_reconcile_test.go +4 -1
- package/engine/tests/parity_testintegrity_test.go +1 -1
- package/pack/.claude/agents/devrites-code-reviewer.md +3 -0
- package/pack/.claude/agents/devrites-devex-reviewer.md +12 -8
- package/pack/.claude/agents/devrites-doubt-reviewer.md +3 -0
- package/pack/.claude/agents/devrites-forge-judge.md +4 -1
- package/pack/.claude/agents/devrites-frontend-reviewer.md +3 -0
- package/pack/.claude/agents/devrites-performance-reviewer.md +10 -9
- package/pack/.claude/agents/devrites-plan-reviewer.md +3 -0
- package/pack/.claude/agents/devrites-proof-runner.md +14 -14
- package/pack/.claude/agents/devrites-retrospector.md +3 -0
- package/pack/.claude/agents/devrites-security-auditor.md +3 -0
- package/pack/.claude/agents/devrites-simplifier-reviewer.md +3 -0
- package/pack/.claude/agents/devrites-slice-wright.md +6 -5
- package/pack/.claude/agents/devrites-spec-reviewer.md +3 -0
- package/pack/.claude/agents/devrites-strategy-reviewer.md +3 -0
- package/pack/.claude/agents/devrites-test-analyst.md +3 -0
- package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +8 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +10 -0
- package/pack/.claude/skills/devrites-source-driven/SKILL.md +1 -1
- package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +2 -2
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +28 -4
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +7 -5
- package/pack/.claude/skills/rite-prove/SKILL.md +25 -14
- package/pack/.claude/skills/rite-temper/SKILL.md +1 -1
- package/pack/.claude/skills/rite-upgrade/SKILL.md +1 -1
- package/pack/.claude/skills/rite-vet/SKILL.md +18 -15
- package/pack/generated/claude/agents/devrites-code-reviewer.md +3 -0
- package/pack/generated/claude/agents/devrites-devex-reviewer.md +12 -8
- package/pack/generated/claude/agents/devrites-doubt-reviewer.md +3 -0
- package/pack/generated/claude/agents/devrites-forge-judge.md +4 -1
- package/pack/generated/claude/agents/devrites-frontend-reviewer.md +3 -0
- package/pack/generated/claude/agents/devrites-performance-reviewer.md +10 -9
- package/pack/generated/claude/agents/devrites-plan-reviewer.md +3 -0
- package/pack/generated/claude/agents/devrites-proof-runner.md +14 -14
- package/pack/generated/claude/agents/devrites-retrospector.md +3 -0
- package/pack/generated/claude/agents/devrites-security-auditor.md +3 -0
- package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +3 -0
- package/pack/generated/claude/agents/devrites-slice-wright.md +6 -5
- package/pack/generated/claude/agents/devrites-spec-reviewer.md +3 -0
- package/pack/generated/claude/agents/devrites-strategy-reviewer.md +3 -0
- package/pack/generated/claude/agents/devrites-test-analyst.md +3 -0
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +8 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +10 -0
- package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +2 -2
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +28 -4
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +7 -5
- package/pack/generated/claude/skills/rite-prove/SKILL.md +25 -14
- package/pack/generated/claude/skills/rite-temper/SKILL.md +1 -1
- package/pack/generated/claude/skills/rite-upgrade/SKILL.md +1 -1
- package/pack/generated/claude/skills/rite-vet/SKILL.md +18 -15
- package/pack/generated/codex/agents/devrites-code-reviewer.toml +3 -0
- package/pack/generated/codex/agents/devrites-devex-reviewer.toml +11 -7
- package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +3 -0
- package/pack/generated/codex/agents/devrites-forge-judge.toml +4 -1
- package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +3 -0
- package/pack/generated/codex/agents/devrites-performance-reviewer.toml +9 -8
- package/pack/generated/codex/agents/devrites-plan-reviewer.toml +3 -0
- package/pack/generated/codex/agents/devrites-proof-runner.toml +13 -13
- package/pack/generated/codex/agents/devrites-retrospector.toml +3 -0
- package/pack/generated/codex/agents/devrites-security-auditor.toml +3 -0
- package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +3 -0
- package/pack/generated/codex/agents/devrites-slice-wright.toml +6 -5
- package/pack/generated/codex/agents/devrites-spec-reviewer.toml +3 -0
- package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +3 -0
- package/pack/generated/codex/agents/devrites-test-analyst.toml +3 -0
- package/pack/generated/codex/hooks.json +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +8 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +10 -0
- package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +2 -2
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +28 -4
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +7 -5
- package/pack/generated/codex/skills/rite-prove/SKILL.md +25 -14
- package/pack/generated/codex/skills/rite-temper/SKILL.md +1 -1
- package/pack/generated/codex/skills/rite-upgrade/SKILL.md +1 -1
- package/pack/generated/codex/skills/rite-vet/SKILL.md +18 -15
- package/package.json +1 -1
- package/scripts/codex-generate.sh +1 -1
|
@@ -7,6 +7,7 @@ package main_test
|
|
|
7
7
|
import (
|
|
8
8
|
"bytes"
|
|
9
9
|
"encoding/json"
|
|
10
|
+
"fmt"
|
|
10
11
|
"os"
|
|
11
12
|
"path/filepath"
|
|
12
13
|
"strconv"
|
|
@@ -235,6 +236,18 @@ func parsePermissionDecision(t *testing.T, stdout string) (decision, reason stri
|
|
|
235
236
|
return env.HookSpecificOutput.PermissionDecision, env.HookSpecificOutput.PermissionDecisionReason
|
|
236
237
|
}
|
|
237
238
|
|
|
239
|
+
func parseStopDecision(t *testing.T, stdout string) (decision, reason string) {
|
|
240
|
+
t.Helper()
|
|
241
|
+
var env struct {
|
|
242
|
+
Decision string `json:"decision"`
|
|
243
|
+
Reason string `json:"reason"`
|
|
244
|
+
}
|
|
245
|
+
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout)), &env); err != nil {
|
|
246
|
+
t.Fatalf("stdout is not a valid Stop decision: %v\n%s", err, stdout)
|
|
247
|
+
}
|
|
248
|
+
return env.Decision, env.Reason
|
|
249
|
+
}
|
|
250
|
+
|
|
238
251
|
func TestHookReviewerReadonlyEnforceDeniesMutatingBash(t *testing.T) {
|
|
239
252
|
for _, h := range []string{"claude", "codex"} {
|
|
240
253
|
h := h
|
|
@@ -259,14 +272,22 @@ func TestHookReviewerReadonlyEnforceDeniesMutatingBash(t *testing.T) {
|
|
|
259
272
|
|
|
260
273
|
func TestHookReviewerReadonlyAllowsSafeBash(t *testing.T) {
|
|
261
274
|
root := newWorkspace(t)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
"
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
275
|
+
for _, command := range []string{
|
|
276
|
+
"grep -rn foo .",
|
|
277
|
+
`cd "` + filepath.Dir(root) + `" && git diff --binary --full-index --no-renames HEAD HEAD --`,
|
|
278
|
+
} {
|
|
279
|
+
in := hookPayload(t, map[string]any{
|
|
280
|
+
"tool_name": "Bash",
|
|
281
|
+
"tool_input": map[string]any{"command": command},
|
|
282
|
+
})
|
|
283
|
+
out, _, code := runDevritesIO(t, root, in, []string{"DEVRITES_REVIEWER_RO=enforce"},
|
|
284
|
+
"hook", "reviewer-readonly", "--harness=claude")
|
|
285
|
+
if code != 0 {
|
|
286
|
+
t.Fatalf("%q exit = %d, want 0", command, code)
|
|
287
|
+
}
|
|
288
|
+
if strings.TrimSpace(out) != "" {
|
|
289
|
+
t.Errorf("%q stdout = %q, want silent for a read-only command", command, out)
|
|
290
|
+
}
|
|
270
291
|
}
|
|
271
292
|
}
|
|
272
293
|
|
|
@@ -379,6 +400,40 @@ func TestCodexGenericCompatibilityDoesNotShadowRoot(t *testing.T) {
|
|
|
379
400
|
}
|
|
380
401
|
}
|
|
381
402
|
|
|
403
|
+
func TestCodexRootRunsArtifactGatesInsideActiveWindow(t *testing.T) {
|
|
404
|
+
root := newWorkspace(t)
|
|
405
|
+
writeActive(t, root, "auth-tokens")
|
|
406
|
+
workspace := filepath.Join(root, "features", "auth-tokens")
|
|
407
|
+
if err := os.WriteFile(filepath.Join(workspace, ".reconcile-base"), []byte("snapshot\n"), 0o600); err != nil {
|
|
408
|
+
t.Fatal(err)
|
|
409
|
+
}
|
|
410
|
+
env := []string{
|
|
411
|
+
"DEVRITES_CODEX_GENERIC_AGENT_COMPAT=1",
|
|
412
|
+
"DEVRITES_HOOK_PROFILE=strict",
|
|
413
|
+
}
|
|
414
|
+
for _, command := range []string{
|
|
415
|
+
"npm run build",
|
|
416
|
+
"npm run test:e2e -- tests/admin-locations.e2e.spec.ts",
|
|
417
|
+
} {
|
|
418
|
+
in := fmt.Sprintf(`{"tool_name":"Bash","tool_input":{"command":%q}}`, command)
|
|
419
|
+
out, errOut, code := runDevritesIO(t, root, in, env,
|
|
420
|
+
"hook", "wright-scope", "--harness=codex")
|
|
421
|
+
if code != 0 || strings.TrimSpace(out) != "" {
|
|
422
|
+
t.Fatalf("%q should be root-owned: exit=%d out=%q stderr=%q", command, code, out, errOut)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
in := `{"tool_name":"Bash","tool_input":{"command":"printf x > src/app.go"}}`
|
|
427
|
+
out, errOut, code := runDevritesIO(t, root, in, env,
|
|
428
|
+
"hook", "wright-scope", "--harness=codex")
|
|
429
|
+
if code != 0 {
|
|
430
|
+
t.Fatalf("source write guard exit=%d stderr=%q", code, errOut)
|
|
431
|
+
}
|
|
432
|
+
if decision, _ := parsePermissionDecision(t, out); decision != "deny" {
|
|
433
|
+
t.Fatalf("root source mutation was not denied: %q", out)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
382
437
|
func TestHookReviewerReadonlyActiveLeafDeniesEveryMutationSurface(t *testing.T) {
|
|
383
438
|
root := newWorkspace(t)
|
|
384
439
|
env := []string{
|
|
@@ -405,20 +460,87 @@ func TestHookReviewerReadonlyActiveLeafDeniesEveryMutationSurface(t *testing.T)
|
|
|
405
460
|
|
|
406
461
|
func TestHookReviewerReadonlyActiveLeafAllowsBoundedProof(t *testing.T) {
|
|
407
462
|
root := newWorkspace(t)
|
|
408
|
-
|
|
409
|
-
out, _, code := runDevritesIO(t, root, in, []string{
|
|
463
|
+
env := []string{
|
|
410
464
|
"DEVRITES_AGENT_RUN=1",
|
|
411
465
|
"DEVRITES_ACTIVE_AGENT=devrites-proof-runner",
|
|
412
|
-
}
|
|
466
|
+
}
|
|
467
|
+
for _, command := range []string{
|
|
468
|
+
"go test ./... -count=1",
|
|
469
|
+
"command -v devrites-engine",
|
|
470
|
+
"devrites-engine conventions orient",
|
|
471
|
+
"devrites-engine learnings mine",
|
|
472
|
+
} {
|
|
473
|
+
in := fmt.Sprintf(`{"tool_name":"Bash","tool_input":{"command":%q}}`, command)
|
|
474
|
+
out, _, code := runDevritesIO(t, root, in, env,
|
|
475
|
+
"hook", "reviewer-readonly", "--harness=claude")
|
|
476
|
+
if code != 0 || strings.TrimSpace(out) != "" {
|
|
477
|
+
t.Fatalf("%q should be allowed: exit=%d out=%q", command, code, out)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
in := `{"tool_name":"Bash","tool_input":{"command":"go test ./... -count=1"}}`
|
|
481
|
+
out, _, code := runDevritesIO(t, root, in, env,
|
|
482
|
+
"hook", "wright-scope", "--harness=claude")
|
|
413
483
|
if code != 0 || strings.TrimSpace(out) != "" {
|
|
414
|
-
t.Fatalf("
|
|
484
|
+
t.Fatalf("wright hook must not shadow reviewer policy: exit=%d out=%q", code, out)
|
|
415
485
|
}
|
|
416
|
-
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
func TestHookReviewerReadonlyProofRunnerKeepsArtifactProofRootOwned(t *testing.T) {
|
|
489
|
+
root := newWorkspace(t)
|
|
490
|
+
env := []string{
|
|
417
491
|
"DEVRITES_AGENT_RUN=1",
|
|
418
492
|
"DEVRITES_ACTIVE_AGENT=devrites-proof-runner",
|
|
419
|
-
}
|
|
493
|
+
}
|
|
494
|
+
for _, command := range []string{
|
|
495
|
+
"npm run build",
|
|
496
|
+
"npm run test:e2e -- tests/admin-locations.e2e.spec.ts",
|
|
497
|
+
"go build ./...",
|
|
498
|
+
"bash scripts/mutate-repository.sh",
|
|
499
|
+
"npm run deploy",
|
|
500
|
+
"make deploy",
|
|
501
|
+
"docker compose up -d",
|
|
502
|
+
"printf x > src/app.go",
|
|
503
|
+
"npm install left-pad",
|
|
504
|
+
"python -c 'open(\"src/app.go\", \"w\").write(\"x\")'",
|
|
505
|
+
} {
|
|
506
|
+
in := fmt.Sprintf(`{"tool_name":"Bash","tool_input":{"command":%q}}`, command)
|
|
507
|
+
out, errOut, code := runDevritesIO(t, root, in, env,
|
|
508
|
+
"hook", "reviewer-readonly", "--harness=claude")
|
|
509
|
+
if code != 0 {
|
|
510
|
+
t.Fatalf("root-owned proof command guard exit=%d stderr=%q", code, errOut)
|
|
511
|
+
}
|
|
512
|
+
if decision, _ := parsePermissionDecision(t, out); decision != "deny" {
|
|
513
|
+
t.Fatalf("proof leaf command was not denied: command=%q out=%q", command, out)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
func TestHookRedwatchRecognizesCodexShellAliases(t *testing.T) {
|
|
519
|
+
root := newWorkspace(t)
|
|
520
|
+
writeActive(t, root, "auth-tokens")
|
|
521
|
+
red := filepath.Join(root, "features", "auth-tokens", ".red")
|
|
522
|
+
|
|
523
|
+
fail := `{"tool_name":"functions.exec_command","tool_input":{"cmd":"npm test"},"tool_response":"Tests: 1 failed, 3 passed"}`
|
|
524
|
+
out, errOut, code := runDevritesIO(t, root, fail, nil,
|
|
525
|
+
"hook", "redwatch", "--harness=codex")
|
|
526
|
+
if code != 0 {
|
|
527
|
+
t.Fatalf("failing exec_command exit=%d stderr=%q", code, errOut)
|
|
528
|
+
}
|
|
529
|
+
if !strings.Contains(out, "tests/build are RED") {
|
|
530
|
+
t.Fatalf("failing exec_command omitted RED context: %q", out)
|
|
531
|
+
}
|
|
532
|
+
if _, err := os.Stat(red); err != nil {
|
|
533
|
+
t.Fatalf("failing exec_command did not create .red: %v", err)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
pass := `{"tool_name":"exec_command","tool_input":{"cmd":"npm test"},"tool_response":"PASS: all tests passed"}`
|
|
537
|
+
out, errOut, code = runDevritesIO(t, root, pass, nil,
|
|
538
|
+
"hook", "redwatch", "--harness=codex")
|
|
420
539
|
if code != 0 || strings.TrimSpace(out) != "" {
|
|
421
|
-
t.Fatalf("
|
|
540
|
+
t.Fatalf("passing exec_command exit=%d out=%q stderr=%q", code, out, errOut)
|
|
541
|
+
}
|
|
542
|
+
if _, err := os.Stat(red); !os.IsNotExist(err) {
|
|
543
|
+
t.Fatalf("passing exec_command retained .red: %v", err)
|
|
422
544
|
}
|
|
423
545
|
}
|
|
424
546
|
|
|
@@ -467,7 +589,7 @@ func TestHookWrightScopeUsesExactOrchestratorAllowlist(t *testing.T) {
|
|
|
467
589
|
}
|
|
468
590
|
}
|
|
469
591
|
|
|
470
|
-
func
|
|
592
|
+
func TestHookWrightScopeRequiresBoundCodexWorkerDuringReconcileWindow(t *testing.T) {
|
|
471
593
|
root := newWorkspace(t)
|
|
472
594
|
writeActive(t, root, "auth-tokens")
|
|
473
595
|
workspace := filepath.Join(root, "features", "auth-tokens")
|
|
@@ -484,7 +606,7 @@ func TestHookWrightScopeBindsCodexWorkerDuringReconcileWindow(t *testing.T) {
|
|
|
484
606
|
input string
|
|
485
607
|
deny bool
|
|
486
608
|
}{
|
|
487
|
-
{"listed edit", `{"tool_name":"Edit","tool_input":{"file_path":"src/app.go"},"agent_type":"worker"}`,
|
|
609
|
+
{"unbound listed edit", `{"tool_name":"Edit","tool_input":{"file_path":"src/app.go"},"agent_type":"worker"}`, true},
|
|
488
610
|
{"unlisted edit", `{"tool_name":"Edit","tool_input":{"file_path":"src/other.go"},"agent_type":"worker"}`, true},
|
|
489
611
|
} {
|
|
490
612
|
t.Run(test.name, func(t *testing.T) {
|
|
@@ -514,6 +636,90 @@ func TestHookWrightScopeBindsCodexWorkerDuringReconcileWindow(t *testing.T) {
|
|
|
514
636
|
}
|
|
515
637
|
}
|
|
516
638
|
|
|
639
|
+
func TestHookWrightScopeAllowsOnlyBoundCodexV1WorkerPaths(t *testing.T) {
|
|
640
|
+
root := newWorkspace(t)
|
|
641
|
+
writeActive(t, root, "auth-tokens")
|
|
642
|
+
workspace := filepath.Join(root, "features", "auth-tokens")
|
|
643
|
+
for name, body := range map[string]string{
|
|
644
|
+
".wright-allowlist": "src/app.go\n",
|
|
645
|
+
".reconcile-base": "snapshot\n",
|
|
646
|
+
".reconcile-allowlist": "src/app.go\n",
|
|
647
|
+
".reconcile-devrites": "[]\n",
|
|
648
|
+
} {
|
|
649
|
+
if err := os.WriteFile(filepath.Join(workspace, name), []byte(body), 0o600); err != nil {
|
|
650
|
+
t.Fatal(err)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if err := os.MkdirAll(filepath.Join(workspace, ".reconcile-objects"), 0o700); err != nil {
|
|
654
|
+
t.Fatal(err)
|
|
655
|
+
}
|
|
656
|
+
sessionID, turnID := "session-bound-worker", "turn-bound-worker"
|
|
657
|
+
writeCodexAgentContract(t, root, "devrites-slice-wright")
|
|
658
|
+
writeCodexSkillContract(t, root, "rite-build", "devrites-slice-wright")
|
|
659
|
+
runDevritesIO(t, root, hookPayload(t, map[string]any{
|
|
660
|
+
"hook_event_name": "UserPromptSubmit",
|
|
661
|
+
"session_id": sessionID,
|
|
662
|
+
"turn_id": turnID,
|
|
663
|
+
"prompt": "$rite-build",
|
|
664
|
+
}), nil, "hook", "agent-dispatch", "--harness=codex")
|
|
665
|
+
if out, stderr, code := runDevritesIO(t, root, hookPayload(t, map[string]any{
|
|
666
|
+
"hook_event_name": "PreToolUse",
|
|
667
|
+
"session_id": sessionID,
|
|
668
|
+
"turn_id": turnID,
|
|
669
|
+
"tool_name": "spawn_agent",
|
|
670
|
+
"tool_use_id": "spawn-bound-worker",
|
|
671
|
+
"tool_input": map[string]any{
|
|
672
|
+
"agent_type": "worker",
|
|
673
|
+
"fork_turns": "none",
|
|
674
|
+
"message": "Read .codex/agents/devrites-slice-wright.toml.",
|
|
675
|
+
},
|
|
676
|
+
}), nil, "hook", "agent-dispatch", "--harness=codex"); code != 0 {
|
|
677
|
+
t.Fatalf("spawn exit=%d out=%s stderr=%s", code, out, stderr)
|
|
678
|
+
} else if decision, reason := parsePermissionDecision(t, out); decision != "allow" {
|
|
679
|
+
t.Fatalf("spawn denied: %s (%s)", decision, reason)
|
|
680
|
+
}
|
|
681
|
+
if out, stderr, code := runDevritesIO(t, root, hookPayload(t, map[string]any{
|
|
682
|
+
"hook_event_name": "SubagentStart",
|
|
683
|
+
"session_id": sessionID,
|
|
684
|
+
"turn_id": "child-turn",
|
|
685
|
+
"agent_id": "bound-worker",
|
|
686
|
+
"agent_type": "worker",
|
|
687
|
+
}), nil, "hook", "subagent-orient", "--harness=codex"); code != 0 ||
|
|
688
|
+
!strings.Contains(out, "Confirmed role") {
|
|
689
|
+
t.Fatalf("start exit=%d out=%s stderr=%s", code, out, stderr)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
env := []string{"DEVRITES_CODEX_GENERIC_AGENT_COMPAT=1", "DEVRITES_HOOK_PROFILE=strict"}
|
|
693
|
+
for _, test := range []struct {
|
|
694
|
+
path string
|
|
695
|
+
deny bool
|
|
696
|
+
}{
|
|
697
|
+
{"src/app.go", false},
|
|
698
|
+
{"src/other.go", true},
|
|
699
|
+
} {
|
|
700
|
+
input := hookPayload(t, map[string]any{
|
|
701
|
+
"hook_event_name": "PreToolUse",
|
|
702
|
+
"session_id": sessionID,
|
|
703
|
+
"agent_id": "bound-worker",
|
|
704
|
+
"agent_type": "worker",
|
|
705
|
+
"tool_name": "Edit",
|
|
706
|
+
"tool_input": map[string]any{"file_path": test.path},
|
|
707
|
+
})
|
|
708
|
+
out, stderr, code := runDevritesIO(t, root, input, env,
|
|
709
|
+
"hook", "wright-scope", "--harness=codex")
|
|
710
|
+
if code != 0 {
|
|
711
|
+
t.Fatalf("%s exit=%d stderr=%s", test.path, code, stderr)
|
|
712
|
+
}
|
|
713
|
+
if test.deny {
|
|
714
|
+
if decision, _ := parsePermissionDecision(t, out); decision != "deny" {
|
|
715
|
+
t.Fatalf("%s should be denied: %s", test.path, out)
|
|
716
|
+
}
|
|
717
|
+
} else if strings.TrimSpace(out) != "" {
|
|
718
|
+
t.Fatalf("%s should be allowed: %s", test.path, out)
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
517
723
|
func TestHookWrightScopeAcceptsCodexV2NamedRole(t *testing.T) {
|
|
518
724
|
root := newWorkspace(t)
|
|
519
725
|
writeActive(t, root, "auth-tokens")
|
|
@@ -594,6 +800,8 @@ func TestHookWrightScopeRequiresSpawnedCodexWorkerForSourceWrites(t *testing.T)
|
|
|
594
800
|
runGuard(`{"tool_name":"Write","tool_input":{"file_path":"src/app.go"}}`, true)
|
|
595
801
|
runGuard(`{"tool_name":"Bash","tool_input":{"command":"printf x > src/app.go"}}`, true)
|
|
596
802
|
runGuard(`{"tool_name":"js","tool_input":{"code":"writeFile()"}}`, true)
|
|
803
|
+
runGuard(`{"tool_name":"Bash","tool_input":{"command":"devrites-engine reconcile restore-check auth-tokens"}}`, false)
|
|
804
|
+
runGuard(`{"tool_name":"Bash","tool_input":{"command":"devrites-engine reconcile restore-check auth-tokens && printf x > src/app.go"}}`, true)
|
|
597
805
|
out, errOut, code := runDevritesIO(t, root,
|
|
598
806
|
`{"tool_name":"Bash","tool_input":{"command":"printf x > src/app.go"}}`,
|
|
599
807
|
nil, "hook", "wright-scope", "--harness=claude")
|
|
@@ -1509,6 +1717,74 @@ func TestCodexAgentDispatchBlocksFalseWaitAndStop(t *testing.T) {
|
|
|
1509
1717
|
}
|
|
1510
1718
|
}
|
|
1511
1719
|
|
|
1720
|
+
func TestCodexAgentDispatchRequiresSuccessfulReasonBoundWaiver(t *testing.T) {
|
|
1721
|
+
root := newWorkspace(t)
|
|
1722
|
+
role := "devrites-plan-reviewer"
|
|
1723
|
+
writeCodexAgentContract(t, root, role)
|
|
1724
|
+
writeCodexSkillContract(t, root, "rite-vet", role)
|
|
1725
|
+
sessionID, turnID := "session-waiver", "turn-waiver"
|
|
1726
|
+
runDevritesIO(t, root, hookPayload(t, map[string]any{
|
|
1727
|
+
"hook_event_name": "UserPromptSubmit",
|
|
1728
|
+
"session_id": sessionID,
|
|
1729
|
+
"turn_id": turnID,
|
|
1730
|
+
"prompt": "$rite-vet",
|
|
1731
|
+
}), nil, "hook", "agent-dispatch", "--harness=codex")
|
|
1732
|
+
|
|
1733
|
+
invalid := hookPayload(t, map[string]any{
|
|
1734
|
+
"hook_event_name": "PreToolUse",
|
|
1735
|
+
"session_id": sessionID,
|
|
1736
|
+
"turn_id": turnID,
|
|
1737
|
+
"tool_name": "functions.exec_command",
|
|
1738
|
+
"tool_input": map[string]any{"cmd": "devrites-engine dispatch-waive wrong-phase && true"},
|
|
1739
|
+
})
|
|
1740
|
+
out, _, _ := runDevritesIO(t, root, invalid, nil,
|
|
1741
|
+
"hook", "agent-dispatch", "--harness=codex")
|
|
1742
|
+
if decision, _ := parsePermissionDecision(t, out); decision != "deny" {
|
|
1743
|
+
t.Fatalf("chained waiver was not denied: %s", out)
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
command := "devrites-engine dispatch-waive wrong-phase"
|
|
1747
|
+
pre := hookPayload(t, map[string]any{
|
|
1748
|
+
"hook_event_name": "PreToolUse",
|
|
1749
|
+
"session_id": sessionID,
|
|
1750
|
+
"turn_id": turnID,
|
|
1751
|
+
"tool_name": "functions.exec_command",
|
|
1752
|
+
"tool_input": map[string]any{"cmd": command},
|
|
1753
|
+
})
|
|
1754
|
+
if out, stderr, code := runDevritesIO(t, root, pre, nil,
|
|
1755
|
+
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1756
|
+
t.Fatalf("waiver pre exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
stop := hookPayload(t, map[string]any{
|
|
1760
|
+
"hook_event_name": "Stop",
|
|
1761
|
+
"session_id": sessionID,
|
|
1762
|
+
"turn_id": turnID,
|
|
1763
|
+
})
|
|
1764
|
+
out, _, _ = runDevritesIO(t, root, stop, nil,
|
|
1765
|
+
"hook", "agent-dispatch", "--harness=codex")
|
|
1766
|
+
if decision, _ := parseStopDecision(t, out); decision != "block" {
|
|
1767
|
+
t.Fatalf("PreToolUse waiver falsely satisfied dispatch: %s", out)
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
post := hookPayload(t, map[string]any{
|
|
1771
|
+
"hook_event_name": "PostToolUse",
|
|
1772
|
+
"session_id": sessionID,
|
|
1773
|
+
"turn_id": turnID,
|
|
1774
|
+
"tool_name": "functions.exec_command",
|
|
1775
|
+
"tool_input": map[string]any{"cmd": command},
|
|
1776
|
+
"tool_response": "dispatch-waive: accepted wrong-phase",
|
|
1777
|
+
})
|
|
1778
|
+
if out, stderr, code := runDevritesIO(t, root, post, nil,
|
|
1779
|
+
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1780
|
+
t.Fatalf("waiver post exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1781
|
+
}
|
|
1782
|
+
if out, stderr, code := runDevritesIO(t, root, stop, nil,
|
|
1783
|
+
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1784
|
+
t.Fatalf("waived stop exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1512
1788
|
func TestCodexAgentDispatchConfirmsGenericRoleAndResult(t *testing.T) {
|
|
1513
1789
|
root := newWorkspace(t)
|
|
1514
1790
|
sessionID, turnID := "session-complete", "turn-complete"
|
|
@@ -1629,12 +1905,99 @@ func TestCodexAgentDispatchConfirmsGenericRoleAndResult(t *testing.T) {
|
|
|
1629
1905
|
t.Fatalf("wait exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1630
1906
|
}
|
|
1631
1907
|
|
|
1908
|
+
if out, stderr, code := runDevritesIO(t, root, stop, nil,
|
|
1909
|
+
"hook", "agent-dispatch", "--harness=codex"); code != 0 {
|
|
1910
|
+
t.Fatalf("pre-wait stop exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1911
|
+
} else if decision, _ := parseStopDecision(t, out); decision != "block" {
|
|
1912
|
+
t.Fatalf("PreToolUse wait falsely satisfied dispatch: %s", out)
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
waitTimeout := hookPayload(t, map[string]any{
|
|
1916
|
+
"hook_event_name": "PostToolUse",
|
|
1917
|
+
"session_id": sessionID,
|
|
1918
|
+
"turn_id": turnID,
|
|
1919
|
+
"tool_name": "wait",
|
|
1920
|
+
"tool_input": map[string]any{"ids": []string{"agent-1"}},
|
|
1921
|
+
"tool_response": map[string]any{"timed_out": true},
|
|
1922
|
+
})
|
|
1923
|
+
if out, stderr, code := runDevritesIO(t, root, waitTimeout, nil,
|
|
1924
|
+
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1925
|
+
t.Fatalf("timed-out wait post exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1926
|
+
}
|
|
1927
|
+
if out, stderr, code := runDevritesIO(t, root, stop, nil,
|
|
1928
|
+
"hook", "agent-dispatch", "--harness=codex"); code != 0 {
|
|
1929
|
+
t.Fatalf("post-timeout stop exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1930
|
+
} else if decision, _ := parseStopDecision(t, out); decision != "block" {
|
|
1931
|
+
t.Fatalf("timed-out wait falsely satisfied dispatch: %s", out)
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
waitDone := hookPayload(t, map[string]any{
|
|
1935
|
+
"hook_event_name": "PostToolUse",
|
|
1936
|
+
"session_id": sessionID,
|
|
1937
|
+
"turn_id": turnID,
|
|
1938
|
+
"tool_name": "wait",
|
|
1939
|
+
"tool_input": map[string]any{"ids": []string{"agent-1"}},
|
|
1940
|
+
"tool_response": map[string]any{"status": "completed"},
|
|
1941
|
+
})
|
|
1942
|
+
if out, stderr, code := runDevritesIO(t, root, waitDone, nil,
|
|
1943
|
+
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1944
|
+
t.Fatalf("wait post exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1632
1947
|
if out, stderr, code := runDevritesIO(t, root, stop, nil,
|
|
1633
1948
|
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1634
1949
|
t.Fatalf("resolved stop exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1635
1950
|
}
|
|
1636
1951
|
}
|
|
1637
1952
|
|
|
1953
|
+
func TestCodexAgentDispatchRefusesAmbiguousGenericStart(t *testing.T) {
|
|
1954
|
+
root := newWorkspace(t)
|
|
1955
|
+
sessionID, turnID := "session-ambiguous-start", "turn-ambiguous-start"
|
|
1956
|
+
roles := []string{"devrites-security-auditor", "devrites-plan-reviewer"}
|
|
1957
|
+
for _, role := range roles {
|
|
1958
|
+
writeCodexAgentContract(t, root, role)
|
|
1959
|
+
}
|
|
1960
|
+
writeCodexSkillContract(t, root, "devrites-audit", strings.Join(roles, ","))
|
|
1961
|
+
runDevritesIO(t, root, hookPayload(t, map[string]any{
|
|
1962
|
+
"hook_event_name": "UserPromptSubmit",
|
|
1963
|
+
"session_id": sessionID,
|
|
1964
|
+
"turn_id": turnID,
|
|
1965
|
+
"prompt": "$devrites-audit",
|
|
1966
|
+
}), nil, "hook", "agent-dispatch", "--harness=codex")
|
|
1967
|
+
|
|
1968
|
+
for i, role := range roles {
|
|
1969
|
+
out, stderr, code := runDevritesIO(t, root, hookPayload(t, map[string]any{
|
|
1970
|
+
"hook_event_name": "PreToolUse",
|
|
1971
|
+
"session_id": sessionID,
|
|
1972
|
+
"turn_id": turnID,
|
|
1973
|
+
"tool_name": "spawn_agent",
|
|
1974
|
+
"tool_use_id": fmt.Sprintf("spawn-%d", i),
|
|
1975
|
+
"tool_input": map[string]any{
|
|
1976
|
+
"agent_type": "explorer",
|
|
1977
|
+
"fork_turns": "none",
|
|
1978
|
+
"message": "Read .codex/agents/" + role + ".toml.",
|
|
1979
|
+
},
|
|
1980
|
+
}), nil, "hook", "agent-dispatch", "--harness=codex")
|
|
1981
|
+
if code != 0 {
|
|
1982
|
+
t.Fatalf("spawn %s exit=%d out=%s stderr=%s", role, code, out, stderr)
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
out, stderr, code := runDevritesIO(t, root, hookPayload(t, map[string]any{
|
|
1987
|
+
"hook_event_name": "SubagentStart",
|
|
1988
|
+
"session_id": sessionID,
|
|
1989
|
+
"turn_id": "child-turn",
|
|
1990
|
+
"agent_id": "ambiguous-agent",
|
|
1991
|
+
"agent_type": "explorer",
|
|
1992
|
+
}), nil, "hook", "subagent-orient", "--harness=codex")
|
|
1993
|
+
if code != 0 {
|
|
1994
|
+
t.Fatalf("start exit=%d stderr=%s", code, stderr)
|
|
1995
|
+
}
|
|
1996
|
+
if !strings.Contains(out, "Dispatch binding failed") {
|
|
1997
|
+
t.Fatalf("ambiguous generic start was bound to an arbitrary role: %s", out)
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
|
|
1638
2001
|
func TestCodexAgentDispatchConfirmsDurableV2NamedRole(t *testing.T) {
|
|
1639
2002
|
root := newWorkspace(t)
|
|
1640
2003
|
codeHome := t.TempDir()
|
|
@@ -1925,6 +2288,14 @@ func TestCodexAgentDispatchGatesReconcileCloseOnBoundWrightResult(t *testing.T)
|
|
|
1925
2288
|
"tool_name": "wait",
|
|
1926
2289
|
"tool_input": map[string]any{"ids": []string{"agent-wright"}},
|
|
1927
2290
|
},
|
|
2291
|
+
{
|
|
2292
|
+
"hook_event_name": "PostToolUse",
|
|
2293
|
+
"session_id": sessionID,
|
|
2294
|
+
"turn_id": turnID,
|
|
2295
|
+
"tool_name": "wait",
|
|
2296
|
+
"tool_input": map[string]any{"ids": []string{"agent-wright"}},
|
|
2297
|
+
"tool_response": map[string]any{"status": "completed"},
|
|
2298
|
+
},
|
|
1928
2299
|
} {
|
|
1929
2300
|
hook := "agent-dispatch"
|
|
1930
2301
|
if payload["hook_event_name"] == "SubagentStart" {
|
|
@@ -71,6 +71,9 @@ func TestParityReconcile(t *testing.T) {
|
|
|
71
71
|
t.Run("close-window", func(t *testing.T) {
|
|
72
72
|
work := newRepo(t)
|
|
73
73
|
snapshot(t, work)
|
|
74
|
+
if out, code := runArgv(t, work, libRootEnv(work), "", binPath, "reconcile", "check", "feat"); code != 0 {
|
|
75
|
+
t.Fatalf("pre-close reconcile check = %d, want 0\n%s", code, out)
|
|
76
|
+
}
|
|
74
77
|
out, code := runArgv(t, work, libRootEnv(work), "", binPath, "reconcile", "close", "feat")
|
|
75
78
|
assertGolden(t, out, code)
|
|
76
79
|
})
|
|
@@ -78,7 +81,7 @@ func TestParityReconcile(t *testing.T) {
|
|
|
78
81
|
t.Run("not-git", func(t *testing.T) {
|
|
79
82
|
work := t.TempDir()
|
|
80
83
|
makeFeatureDir(t, work, "feat")
|
|
81
|
-
check(t, work) //
|
|
84
|
+
check(t, work) // fail closed outside Git
|
|
82
85
|
})
|
|
83
86
|
|
|
84
87
|
t.Run("bad-mode", func(t *testing.T) {
|
|
@@ -58,7 +58,7 @@ func TestParityTestIntegrity(t *testing.T) {
|
|
|
58
58
|
runCase(t, newRepo(t), "ghost")
|
|
59
59
|
})
|
|
60
60
|
t.Run("not-git", func(t *testing.T) {
|
|
61
|
-
work := t.TempDir() // no initGitRepo -> gate
|
|
61
|
+
work := t.TempDir() // no initGitRepo -> gate fails closed
|
|
62
62
|
makeFeatureDir(t, work, "feat")
|
|
63
63
|
runCase(t, work, "feat")
|
|
64
64
|
})
|
|
@@ -98,6 +98,9 @@ not as a blocker on this diff.
|
|
|
98
98
|
- If you can't verify something, say so explicitly rather than assuming it's fine.
|
|
99
99
|
|
|
100
100
|
## Output
|
|
101
|
+
|
|
102
|
+
Wrap the report in the standards `agent-result/v1` envelope with
|
|
103
|
+
`payload.type: review-findings`; never return raw prose.
|
|
101
104
|
```
|
|
102
105
|
Code review (<slug>) — independent
|
|
103
106
|
[Critical] file:line — problem. fix.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: devrites-devex-reviewer
|
|
3
3
|
description: Reviews the developer experience of one DevRites feature with fresh context. At /rite-vet it predicts the experience; at /rite-seal it measures and reconciles it. Use for public APIs, CLIs, SDKs, libraries, webhooks, config or environment contracts, error messages, and getting-started flows. Finds where the next developer gets stuck instead of rubber-stamping the surface.
|
|
4
|
-
tools: Read, Grep, Glob
|
|
4
|
+
tools: Read, Grep, Glob
|
|
5
5
|
hooks:
|
|
6
6
|
PreToolUse:
|
|
7
7
|
- matcher: Edit|Write|MultiEdit|NotebookEdit|Bash|Agent|Task
|
|
@@ -48,10 +48,10 @@ You receive a feature slug or workspace path (`.devrites/work/<slug>/`) and the
|
|
|
48
48
|
diff scope. Read `spec.md`, `plan.md`, `decisions.md`, `touched-files.md`, and
|
|
49
49
|
`.devrites/principles.md` if present. Public API invariants in the principles file
|
|
50
50
|
are binding. Read `devex.md` if present for predicted or measured scores, plus
|
|
51
|
-
`evidence.md
|
|
52
|
-
the
|
|
53
|
-
route handlers, CLI entry points, exported signatures, README
|
|
54
|
-
instructions, and error or exit paths.
|
|
51
|
+
`evidence.md`, `browser-evidence.md`, and the root-supplied immutable diff and
|
|
52
|
+
quickstart log for the measured run. Inspect the developer-facing files it
|
|
53
|
+
touches, including route handlers, CLI entry points, exported signatures, README
|
|
54
|
+
or quickstart instructions, and error or exit paths.
|
|
55
55
|
|
|
56
56
|
## Review (developer-facing surface, feature scope only)
|
|
57
57
|
|
|
@@ -62,9 +62,10 @@ consistently:
|
|
|
62
62
|
- **Time-to-hello-world:** in measure mode, record the wall-clock time to one
|
|
63
63
|
successful call or response. In predict mode, estimate it. This is the headline
|
|
64
64
|
number.
|
|
65
|
-
- **Getting-started friction:**
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
- **Getting-started friction:** validate the root-owned clean-checkout quickstart
|
|
66
|
+
transcript, exact commands, timings, and candidate identity. An undocumented
|
|
67
|
+
prerequisite, wrong command, missing step, or stale/mismatched transcript is a
|
|
68
|
+
finding. Never execute the quickstart yourself.
|
|
68
69
|
- **Error-message quality:** a failure must say what failed, why, and how to
|
|
69
70
|
recover, include the relevant IDs, and expose **no secrets** under `security.md`.
|
|
70
71
|
A bare trace, silent exit, or "an error occurred" on a developer-facing path is a
|
|
@@ -96,6 +97,9 @@ consistently:
|
|
|
96
97
|
invent a DX problem to justify the pass.
|
|
97
98
|
|
|
98
99
|
## Output
|
|
100
|
+
|
|
101
|
+
Wrap the report in the standards `agent-result/v1` envelope with
|
|
102
|
+
`payload.type: review-findings`; never return raw prose.
|
|
99
103
|
```
|
|
100
104
|
DevEx review (<slug>) — independent · mode: predict | measure
|
|
101
105
|
Scorecard:
|
|
@@ -54,6 +54,9 @@ Critical. Treat overrides as review input, not permission.
|
|
|
54
54
|
- Be concrete: the exact scenario that breaks it, with `file:line` where relevant.
|
|
55
55
|
|
|
56
56
|
## Output
|
|
57
|
+
|
|
58
|
+
Wrap the report in the standards `agent-result/v1` envelope with
|
|
59
|
+
`payload.type: review-findings`; never return raw prose.
|
|
57
60
|
```
|
|
58
61
|
Doubt review
|
|
59
62
|
Claim: <restated>
|
|
@@ -36,7 +36,7 @@ Read the review bar once: `spec.md` and the slice acceptance criteria;
|
|
|
36
36
|
For each candidate, read only the immutable extracted delta:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
|
|
39
|
+
cd "<validated manifest primary_root>" && git diff --binary --full-index --no-renames \
|
|
40
40
|
"<candidate initial_base>" "<candidate commit>" --
|
|
41
41
|
```
|
|
42
42
|
|
|
@@ -94,6 +94,9 @@ Cite the diff line before the mark, and never rank candidates by general impress
|
|
|
94
94
|
- The strategy a candidate was assigned is context, not credit: score the code it produced.
|
|
95
95
|
|
|
96
96
|
## Output
|
|
97
|
+
|
|
98
|
+
Wrap the report in the standards `agent-result/v1` envelope with
|
|
99
|
+
`payload.type: review-findings`; never return raw prose.
|
|
97
100
|
```
|
|
98
101
|
Forge verdict (<slug> / <slice id>) — independent, comparative
|
|
99
102
|
Candidates scored (each, evidence → mark):
|
|
@@ -72,6 +72,9 @@ project's tokens, shared components, and neighboring screens.
|
|
|
72
72
|
with `file:line` and a concrete fix. Feature scope only.
|
|
73
73
|
|
|
74
74
|
## Output
|
|
75
|
+
|
|
76
|
+
Wrap the report in the standards `agent-result/v1` envelope with
|
|
77
|
+
`payload.type: review-findings`; never return raw prose.
|
|
75
78
|
```
|
|
76
79
|
Frontend review (<slug>) — independent
|
|
77
80
|
System alignment: <drift by root cause>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: devrites-performance-reviewer
|
|
3
3
|
description: Reviews one DevRites feature for /rite-seal from a fresh context, starting with measurement. Checks N+1 queries, hot-path work, payload and bundle size, and Core Web Vitals risks. Source mode reports potential findings from a static scan; Measured mode grades real Lighthouse, PSI, CrUX, or trace results with a source-labeled scorecard. Never claims a slowdown without a number or a concrete measurement, and never presents lab data as field data.
|
|
4
|
-
tools: Read, Grep, Glob
|
|
4
|
+
tools: Read, Grep, Glob
|
|
5
5
|
hooks:
|
|
6
6
|
PreToolUse:
|
|
7
7
|
- matcher: Edit|Write|MultiEdit|NotebookEdit|Bash|Agent|Task
|
|
@@ -29,16 +29,14 @@ remains a Critical. Treat overrides as review input, not permission.
|
|
|
29
29
|
|
|
30
30
|
## Inputs
|
|
31
31
|
In workspace `.devrites/work/<slug>/`, read `spec.md` for any performance budget,
|
|
32
|
-
then `evidence.md
|
|
33
|
-
|
|
32
|
+
then `evidence.md`, `touched-files.md`, and the immutable diff supplied by the root.
|
|
33
|
+
Look for Core Web Vitals evidence in `evidence.md`, a saved Lighthouse,
|
|
34
34
|
PageSpeed Insights, or CrUX JSON artifact, and `browser-evidence.md`.
|
|
35
35
|
|
|
36
|
-
Read the baseline checklist
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
[ -f "$C" ] || C=pack/.claude/skills/rite-review/reference/performance-checklist.md
|
|
41
|
-
```
|
|
36
|
+
Read the first existing baseline checklist in this order with `Read`:
|
|
37
|
+
`.claude/skills/rite-review/reference/performance-checklist.md`,
|
|
38
|
+
the packet-provided plugin-root copy, then
|
|
39
|
+
`pack/.claude/skills/rite-review/reference/performance-checklist.md`.
|
|
42
40
|
|
|
43
41
|
## Two modes (the inputs set the mode, not a flag)
|
|
44
42
|
- **Source mode:** use this default when there are no performance artifacts. Scan
|
|
@@ -87,6 +85,9 @@ when a scorecard is allowed.
|
|
|
87
85
|
|
|
88
86
|
## Output
|
|
89
87
|
|
|
88
|
+
Wrap the report in the standards `agent-result/v1` envelope with
|
|
89
|
+
`payload.type: review-findings`; never return raw prose.
|
|
90
|
+
|
|
90
91
|
**Measured mode**: lead with a compact scorecard, then the line findings:
|
|
91
92
|
```
|
|
92
93
|
Performance review (<slug>) — independent
|