devrites 3.2.16 → 3.2.18
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 +15 -0
- package/README.md +1 -1
- package/engine/Makefile +3 -3
- 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/install/install.go +36 -16
- package/engine/internal/install/install_test.go +50 -1
- package/engine/internal/iohooks/iohooks.go +5 -3
- package/engine/internal/iohooks/iohooks_test.go +11 -0
- package/engine/internal/lib/adoption.go +36 -11
- package/engine/internal/lib/adoption_test.go +30 -0
- package/engine/internal/lib/analyze.go +10 -2
- package/engine/internal/lib/archivesearch.go +41 -9
- package/engine/internal/lib/archivesearch_test.go +28 -0
- package/engine/internal/lib/buildreadiness.go +5 -1
- package/engine/internal/lib/closeout.go +34 -11
- package/engine/internal/lib/closeout_test.go +99 -0
- package/engine/internal/lib/context.go +6 -1
- package/engine/internal/lib/dispatchwaive.go +31 -0
- package/engine/internal/lib/dispatchwaive_test.go +23 -0
- package/engine/internal/lib/extensions.go +5 -1
- package/engine/internal/lib/learnings.go +8 -2
- package/engine/internal/lib/ledger.go +17 -4
- package/engine/internal/lib/ledger_test.go +24 -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/resolve.go +8 -3
- package/engine/internal/lib/resolve_remediation_test.go +14 -0
- package/engine/internal/lib/runbook.go +4 -1
- package/engine/internal/lib/runbook_context_test.go +43 -0
- package/engine/internal/lib/testintegrity.go +4 -4
- package/engine/internal/lib/testintegrity_test.go +15 -0
- package/engine/internal/lib/tickafk.go +6 -2
- package/engine/internal/lib/util.go +128 -23
- package/engine/internal/profile/profile.go +27 -13
- package/engine/internal/profile/profile_test.go +13 -0
- 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 +353 -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 +4 -2
- 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/phase-contract.md +17 -3
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +3 -1
- 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 +4 -2
- 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/phase-contract.md +17 -3
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +3 -1
- 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 +4 -2
- 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/phase-contract.md +17 -3
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +3 -1
- 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
|
@@ -236,6 +236,18 @@ func parsePermissionDecision(t *testing.T, stdout string) (decision, reason stri
|
|
|
236
236
|
return env.HookSpecificOutput.PermissionDecision, env.HookSpecificOutput.PermissionDecisionReason
|
|
237
237
|
}
|
|
238
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
|
+
|
|
239
251
|
func TestHookReviewerReadonlyEnforceDeniesMutatingBash(t *testing.T) {
|
|
240
252
|
for _, h := range []string{"claude", "codex"} {
|
|
241
253
|
h := h
|
|
@@ -260,14 +272,22 @@ func TestHookReviewerReadonlyEnforceDeniesMutatingBash(t *testing.T) {
|
|
|
260
272
|
|
|
261
273
|
func TestHookReviewerReadonlyAllowsSafeBash(t *testing.T) {
|
|
262
274
|
root := newWorkspace(t)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
"
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
+
}
|
|
271
291
|
}
|
|
272
292
|
}
|
|
273
293
|
|
|
@@ -440,20 +460,87 @@ func TestHookReviewerReadonlyActiveLeafDeniesEveryMutationSurface(t *testing.T)
|
|
|
440
460
|
|
|
441
461
|
func TestHookReviewerReadonlyActiveLeafAllowsBoundedProof(t *testing.T) {
|
|
442
462
|
root := newWorkspace(t)
|
|
443
|
-
|
|
444
|
-
out, _, code := runDevritesIO(t, root, in, []string{
|
|
463
|
+
env := []string{
|
|
445
464
|
"DEVRITES_AGENT_RUN=1",
|
|
446
465
|
"DEVRITES_ACTIVE_AGENT=devrites-proof-runner",
|
|
447
|
-
}
|
|
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")
|
|
448
483
|
if code != 0 || strings.TrimSpace(out) != "" {
|
|
449
|
-
t.Fatalf("
|
|
484
|
+
t.Fatalf("wright hook must not shadow reviewer policy: exit=%d out=%q", code, out)
|
|
450
485
|
}
|
|
451
|
-
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
func TestHookReviewerReadonlyProofRunnerKeepsArtifactProofRootOwned(t *testing.T) {
|
|
489
|
+
root := newWorkspace(t)
|
|
490
|
+
env := []string{
|
|
452
491
|
"DEVRITES_AGENT_RUN=1",
|
|
453
492
|
"DEVRITES_ACTIVE_AGENT=devrites-proof-runner",
|
|
454
|
-
}
|
|
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")
|
|
455
539
|
if code != 0 || strings.TrimSpace(out) != "" {
|
|
456
|
-
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)
|
|
457
544
|
}
|
|
458
545
|
}
|
|
459
546
|
|
|
@@ -502,7 +589,7 @@ func TestHookWrightScopeUsesExactOrchestratorAllowlist(t *testing.T) {
|
|
|
502
589
|
}
|
|
503
590
|
}
|
|
504
591
|
|
|
505
|
-
func
|
|
592
|
+
func TestHookWrightScopeRequiresBoundCodexWorkerDuringReconcileWindow(t *testing.T) {
|
|
506
593
|
root := newWorkspace(t)
|
|
507
594
|
writeActive(t, root, "auth-tokens")
|
|
508
595
|
workspace := filepath.Join(root, "features", "auth-tokens")
|
|
@@ -519,7 +606,7 @@ func TestHookWrightScopeBindsCodexWorkerDuringReconcileWindow(t *testing.T) {
|
|
|
519
606
|
input string
|
|
520
607
|
deny bool
|
|
521
608
|
}{
|
|
522
|
-
{"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},
|
|
523
610
|
{"unlisted edit", `{"tool_name":"Edit","tool_input":{"file_path":"src/other.go"},"agent_type":"worker"}`, true},
|
|
524
611
|
} {
|
|
525
612
|
t.Run(test.name, func(t *testing.T) {
|
|
@@ -549,6 +636,90 @@ func TestHookWrightScopeBindsCodexWorkerDuringReconcileWindow(t *testing.T) {
|
|
|
549
636
|
}
|
|
550
637
|
}
|
|
551
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
|
+
|
|
552
723
|
func TestHookWrightScopeAcceptsCodexV2NamedRole(t *testing.T) {
|
|
553
724
|
root := newWorkspace(t)
|
|
554
725
|
writeActive(t, root, "auth-tokens")
|
|
@@ -629,6 +800,8 @@ func TestHookWrightScopeRequiresSpawnedCodexWorkerForSourceWrites(t *testing.T)
|
|
|
629
800
|
runGuard(`{"tool_name":"Write","tool_input":{"file_path":"src/app.go"}}`, true)
|
|
630
801
|
runGuard(`{"tool_name":"Bash","tool_input":{"command":"printf x > src/app.go"}}`, true)
|
|
631
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)
|
|
632
805
|
out, errOut, code := runDevritesIO(t, root,
|
|
633
806
|
`{"tool_name":"Bash","tool_input":{"command":"printf x > src/app.go"}}`,
|
|
634
807
|
nil, "hook", "wright-scope", "--harness=claude")
|
|
@@ -1544,6 +1717,74 @@ func TestCodexAgentDispatchBlocksFalseWaitAndStop(t *testing.T) {
|
|
|
1544
1717
|
}
|
|
1545
1718
|
}
|
|
1546
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
|
+
|
|
1547
1788
|
func TestCodexAgentDispatchConfirmsGenericRoleAndResult(t *testing.T) {
|
|
1548
1789
|
root := newWorkspace(t)
|
|
1549
1790
|
sessionID, turnID := "session-complete", "turn-complete"
|
|
@@ -1664,12 +1905,99 @@ func TestCodexAgentDispatchConfirmsGenericRoleAndResult(t *testing.T) {
|
|
|
1664
1905
|
t.Fatalf("wait exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1665
1906
|
}
|
|
1666
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
|
+
|
|
1667
1947
|
if out, stderr, code := runDevritesIO(t, root, stop, nil,
|
|
1668
1948
|
"hook", "agent-dispatch", "--harness=codex"); code != 0 || strings.TrimSpace(out) != "" {
|
|
1669
1949
|
t.Fatalf("resolved stop exit=%d out=%s stderr=%s", code, out, stderr)
|
|
1670
1950
|
}
|
|
1671
1951
|
}
|
|
1672
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
|
+
|
|
1673
2001
|
func TestCodexAgentDispatchConfirmsDurableV2NamedRole(t *testing.T) {
|
|
1674
2002
|
root := newWorkspace(t)
|
|
1675
2003
|
codeHome := t.TempDir()
|
|
@@ -1960,6 +2288,14 @@ func TestCodexAgentDispatchGatesReconcileCloseOnBoundWrightResult(t *testing.T)
|
|
|
1960
2288
|
"tool_name": "wait",
|
|
1961
2289
|
"tool_input": map[string]any{"ids": []string{"agent-wright"}},
|
|
1962
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
|
+
},
|
|
1963
2299
|
} {
|
|
1964
2300
|
hook := "agent-dispatch"
|
|
1965
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
|
|
@@ -102,6 +102,9 @@ gap remains.
|
|
|
102
102
|
lower its confidence.
|
|
103
103
|
|
|
104
104
|
## Output
|
|
105
|
+
|
|
106
|
+
Wrap the report in the standards `agent-result/v1` envelope with
|
|
107
|
+
`payload.type: review-findings`; never return raw prose.
|
|
105
108
|
```
|
|
106
109
|
Plan review (<slug>) — independent, pre-build
|
|
107
110
|
Dimension bands (evidence → band):
|