devrites 3.2.1 → 3.2.3
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/docs/architecture.md +6 -2
- package/docs/command-map.md +3 -3
- package/docs/engine/agent-contract.md +3 -2
- package/docs/harness-compliance.md +2 -2
- package/docs/orchestration.md +15 -12
- package/docs/porting-to-a-new-harness.md +2 -1
- package/docs/skills.md +7 -2
- package/engine/hooks.go +56 -7
- package/engine/hooks_agent_dispatch.go +1193 -0
- package/engine/hooks_workspace.go +92 -9
- package/engine/internal/forge/forge.go +0 -2
- package/engine/internal/harness/compliance.go +1 -1
- package/engine/internal/harness/harness.go +3 -0
- package/engine/internal/lib/doubtcoverage.go +2 -12
- package/engine/internal/lib/reconcile.go +4 -29
- package/engine/internal/lib/reconcile_test.go +14 -10
- package/engine/main.go +4 -1
- package/engine/tests/hook_test.go +966 -0
- package/engine/tests/lib_parity_test.go +5 -17
- package/engine/tests/parity_reconcile_test.go +1 -1
- package/engine/tests/parity_test.go +2 -6
- package/pack/.claude/skills/devrites-api-interface/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-audit/SKILL.md +5 -6
- package/pack/.claude/skills/devrites-browser-proof/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-doubt/SKILL.md +2 -1
- package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-interview/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-lib/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-lib/reference/model-tiers.md +4 -3
- package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +4 -5
- package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +14 -8
- package/pack/.claude/skills/devrites-prose-craft/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-refresh-indexes/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-source-driven/SKILL.md +1 -0
- package/pack/.claude/skills/devrites-ux-shape/SKILL.md +1 -0
- package/pack/.claude/skills/rite/SKILL.md +1 -0
- package/pack/.claude/skills/rite-adopt/SKILL.md +1 -0
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +1 -0
- package/pack/.claude/skills/rite-build/SKILL.md +2 -1
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +3 -4
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +11 -12
- package/pack/.claude/skills/rite-clarify/SKILL.md +1 -0
- package/pack/.claude/skills/rite-converge/SKILL.md +1 -0
- package/pack/.claude/skills/rite-customize/SKILL.md +1 -0
- package/pack/.claude/skills/rite-define/SKILL.md +1 -0
- package/pack/.claude/skills/rite-doctor/SKILL.md +1 -0
- package/pack/.claude/skills/rite-dogfood/SKILL.md +1 -0
- package/pack/.claude/skills/rite-explain/SKILL.md +4 -2
- package/pack/.claude/skills/rite-frame/SKILL.md +1 -0
- package/pack/.claude/skills/rite-handoff/SKILL.md +1 -0
- package/pack/.claude/skills/rite-learn/SKILL.md +1 -0
- package/pack/.claude/skills/rite-plan/SKILL.md +1 -0
- package/pack/.claude/skills/rite-polish/SKILL.md +1 -0
- package/pack/.claude/skills/rite-pov/SKILL.md +1 -0
- package/pack/.claude/skills/rite-pr-feedback/SKILL.md +1 -0
- package/pack/.claude/skills/rite-pressure-test/SKILL.md +1 -0
- package/pack/.claude/skills/rite-prototype/SKILL.md +1 -0
- package/pack/.claude/skills/rite-prove/SKILL.md +1 -0
- package/pack/.claude/skills/rite-quick/SKILL.md +1 -0
- package/pack/.claude/skills/rite-resolve/SKILL.md +1 -0
- package/pack/.claude/skills/rite-review/SKILL.md +1 -0
- package/pack/.claude/skills/rite-seal/SKILL.md +1 -0
- package/pack/.claude/skills/rite-seal/reference/phase-contract.md +2 -3
- package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +10 -12
- package/pack/.claude/skills/rite-ship/SKILL.md +1 -0
- package/pack/.claude/skills/rite-spec/SKILL.md +1 -0
- package/pack/.claude/skills/rite-status/SKILL.md +1 -0
- package/pack/.claude/skills/rite-temper/SKILL.md +3 -2
- package/pack/.claude/skills/rite-upgrade/SKILL.md +3 -1
- package/pack/.claude/skills/rite-vet/SKILL.md +1 -0
- package/pack/.claude/skills/rite-zoom-out/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-api-interface/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-audit/SKILL.md +5 -6
- package/pack/generated/claude/skills/devrites-browser-proof/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-doubt/SKILL.md +2 -1
- package/pack/generated/claude/skills/devrites-frontend-craft/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-interview/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-lib/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-lib/reference/model-tiers.md +4 -3
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +4 -5
- package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +14 -8
- package/pack/generated/claude/skills/devrites-prose-craft/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-refresh-indexes/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +1 -0
- package/pack/generated/claude/skills/devrites-ux-shape/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-adopt/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-build/SKILL.md +2 -1
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +3 -4
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +11 -12
- package/pack/generated/claude/skills/rite-clarify/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-converge/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-customize/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-define/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-doctor/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-dogfood/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-explain/SKILL.md +4 -2
- package/pack/generated/claude/skills/rite-frame/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-handoff/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-learn/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-plan/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-polish/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-pov/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-pr-feedback/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-pressure-test/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-prototype/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-prove/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-quick/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-resolve/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-review/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-seal/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +2 -3
- package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +10 -12
- package/pack/generated/claude/skills/rite-ship/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-spec/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-status/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-temper/SKILL.md +3 -2
- package/pack/generated/claude/skills/rite-upgrade/SKILL.md +3 -1
- package/pack/generated/claude/skills/rite-vet/SKILL.md +1 -0
- package/pack/generated/claude/skills/rite-zoom-out/SKILL.md +1 -0
- package/pack/generated/codex/AGENTS.md +2 -3
- package/pack/generated/codex/hooks.json +38 -4
- package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-audit/SKILL.md +10 -10
- package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-doubt/SKILL.md +7 -5
- package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-interview/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-lib/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-lib/reference/model-tiers.md +4 -3
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +4 -5
- package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +14 -8
- package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +6 -4
- package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-adopt/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-build/SKILL.md +7 -5
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +3 -4
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +11 -12
- package/pack/generated/codex/skills/rite-clarify/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-converge/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-customize/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-define/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-doctor/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-dogfood/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-explain/SKILL.md +9 -6
- package/pack/generated/codex/skills/rite-frame/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-handoff/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-learn/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-plan/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-polish/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-pov/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-prototype/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-prove/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-quick/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-resolve/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-review/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-seal/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +2 -3
- package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +10 -12
- package/pack/generated/codex/skills/rite-ship/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-spec/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-status/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-temper/SKILL.md +8 -6
- package/pack/generated/codex/skills/rite-upgrade/SKILL.md +8 -5
- package/pack/generated/codex/skills/rite-vet/SKILL.md +6 -4
- package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +6 -4
- package/package.json +1 -1
- package/scripts/codex-generate.sh +46 -16
- package/scripts/live-hosts/agent-result.schema.json +2 -1
- package/scripts/live-hosts/claude.sh +2 -2
- package/scripts/live-hosts/codex.sh +4 -0
- package/scripts/live-hosts/fake-host.py +1 -8
- package/scripts/live-hosts/host-transport.py +1 -2
- package/scripts/npm-audit-exceptions.json +12 -0
- package/scripts/run-agent-contract-evals.py +3 -3
- package/scripts/run-evals.sh +3 -34
- package/scripts/validate-frontmatter.py +34 -1
- package/engine/testdata/golden/TestParityA1Guard/inline-fallback-silent.golden +0 -1
- package/engine/testdata/golden/TestParityDoubtCoverage/arg=inline.golden +0 -3
- package/engine/testdata/golden/TestParityDoubtCoverage/arg=missing-inline.golden +0 -3
- package/scripts/eval-runner.py +0 -275
- package/scripts/eval-scorer.mjs +0 -46
- package/scripts/render-eval-summary.py +0 -48
- /package/engine/testdata/golden/TestParityReconcile/{inline-fallback → close-window}/snapshot.golden +0 -0
- /package/engine/testdata/golden/TestParityReconcile/{inline-fallback.golden → close-window.golden} +0 -0
|
@@ -465,14 +465,10 @@ func hookA1Guard(h harness.Harness, stdin io.Reader, stdout, stderr io.Writer) i
|
|
|
465
465
|
if !ok {
|
|
466
466
|
return exitOK
|
|
467
467
|
}
|
|
468
|
-
// The guard is active between a reconcile snapshot and its check.
|
|
469
|
-
// fallback makes the orchestrator the expected writer.
|
|
468
|
+
// The guard is active between a reconcile snapshot and its check.
|
|
470
469
|
if !wsIsFile(filepath.Join(dir, ".reconcile-base")) {
|
|
471
470
|
return exitOK
|
|
472
471
|
}
|
|
473
|
-
if wsIsFile(filepath.Join(dir, ".reconcile-inline")) {
|
|
474
|
-
return exitOK
|
|
475
|
-
}
|
|
476
472
|
|
|
477
473
|
in := h.ParseGuardInput(bytes.NewReader(data))
|
|
478
474
|
if !isEditTool(in.ToolName) {
|
|
@@ -562,10 +558,22 @@ func hookWrightScope(h harness.Harness, stdin io.Reader, stdout, stderr io.Write
|
|
|
562
558
|
return exitOK
|
|
563
559
|
}
|
|
564
560
|
in := h.ParseGuardInput(bytes.NewReader(data))
|
|
565
|
-
kind :=
|
|
561
|
+
kind := devritesAgentForGuard(h, in)
|
|
562
|
+
genericCompat := os.Getenv("DEVRITES_CODEX_GENERIC_AGENT_COMPAT") == "1"
|
|
563
|
+
if in.AgentType == "" && in.AgentID == "" {
|
|
564
|
+
if code, handled := guardRootBuildWindow(h, data, in, stdout, stderr); handled {
|
|
565
|
+
return code
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if kind == devritesAgentNone &&
|
|
569
|
+
genericCompat &&
|
|
570
|
+
in.AgentType == "" {
|
|
571
|
+
return exitOK
|
|
572
|
+
}
|
|
566
573
|
forgeDeclared := forgeWrightDeclared()
|
|
567
|
-
|
|
568
|
-
|
|
574
|
+
genericWright := kind == devritesAgentGenericWright
|
|
575
|
+
active := kind != devritesAgentNone && (!genericWright || forgeDeclared)
|
|
576
|
+
if forgeDeclared && kind != devritesAgentWright && !genericWright {
|
|
569
577
|
return denyWright(h, stdout, stderr, forgeWrightDenyReason, drvreason.HookForgeBindingDenied)
|
|
570
578
|
}
|
|
571
579
|
if kind == devritesAgentNone && (in.AgentType != "" || os.Getenv("DEVRITES_WRIGHT_AGENT_REQUIRED") == "1") {
|
|
@@ -607,6 +615,12 @@ func hookWrightScope(h harness.Harness, stdin io.Reader, stdout, stderr io.Write
|
|
|
607
615
|
}
|
|
608
616
|
return exitOK
|
|
609
617
|
}
|
|
618
|
+
if genericWright && !forgeDeclared {
|
|
619
|
+
if !wsIsFile(filepath.Join(dir, ".reconcile-base")) {
|
|
620
|
+
return exitOK
|
|
621
|
+
}
|
|
622
|
+
active = true
|
|
623
|
+
}
|
|
610
624
|
|
|
611
625
|
if wrightForbiddenShellRe.MatchString(in.Command) ||
|
|
612
626
|
(opaqueInterpreterRe.MatchString(in.Command) && !safeReadonlyShellCommand(in.Command)) {
|
|
@@ -617,7 +631,7 @@ func hookWrightScope(h harness.Harness, stdin io.Reader, stdout, stderr io.Write
|
|
|
617
631
|
}
|
|
618
632
|
|
|
619
633
|
projectDir := filepath.Dir(root)
|
|
620
|
-
if !forgeDeclared && kind == devritesAgentWright && insideForgeStaging(projectDir) {
|
|
634
|
+
if !forgeDeclared && (kind == devritesAgentWright || genericWright) && insideForgeStaging(projectDir) {
|
|
621
635
|
return denyOrObserveWright(h, stdout, stderr, dir, true, forgeWrightDenyReason, drvreason.HookForgeBindingDenied, []string{"missing Forge binding"})
|
|
622
636
|
}
|
|
623
637
|
if forgeDeclared {
|
|
@@ -994,6 +1008,75 @@ func denyOrObserveWright(h harness.Harness, stdout, stderr io.Writer, dir string
|
|
|
994
1008
|
return exitOK
|
|
995
1009
|
}
|
|
996
1010
|
|
|
1011
|
+
func guardRootBuildWindow(h harness.Harness, data []byte, in harness.GuardInput, stdout, stderr io.Writer) (int, bool) {
|
|
1012
|
+
root, _, dir, ok := resolveWorkspace()
|
|
1013
|
+
if !ok || !wsIsFile(filepath.Join(dir, ".reconcile-base")) {
|
|
1014
|
+
return exitOK, false
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if isAgentDispatchTool(in.ToolName) {
|
|
1018
|
+
return exitOK, true
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
projectDir := filepath.Dir(root)
|
|
1022
|
+
if isOpaqueExecutionTool(in.ToolName) {
|
|
1023
|
+
return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
|
|
1024
|
+
}
|
|
1025
|
+
targets, suspicious := rootMutationTargets(data, in)
|
|
1026
|
+
for _, target := range targets {
|
|
1027
|
+
abs := target
|
|
1028
|
+
if !filepath.IsAbs(abs) {
|
|
1029
|
+
abs = filepath.Join(projectDir, abs)
|
|
1030
|
+
}
|
|
1031
|
+
abs = filepath.Clean(abs)
|
|
1032
|
+
resolved, err := resolveRootMutationTarget(abs)
|
|
1033
|
+
if err != nil {
|
|
1034
|
+
return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
|
|
1035
|
+
}
|
|
1036
|
+
if !underDevrites(resolved, root) {
|
|
1037
|
+
return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if suspicious {
|
|
1041
|
+
return denyWright(h, stdout, stderr, a1DenyReason, drvreason.HookA1Denied), true
|
|
1042
|
+
}
|
|
1043
|
+
if len(targets) > 0 {
|
|
1044
|
+
return exitOK, true
|
|
1045
|
+
}
|
|
1046
|
+
return exitOK, false
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
func resolveRootMutationTarget(filename string) (string, error) {
|
|
1050
|
+
info, err := os.Lstat(filename)
|
|
1051
|
+
if err == nil && info.Mode()&os.ModeSymlink != 0 {
|
|
1052
|
+
target, err := os.Readlink(filename)
|
|
1053
|
+
if err != nil {
|
|
1054
|
+
return "", err
|
|
1055
|
+
}
|
|
1056
|
+
if !filepath.IsAbs(target) {
|
|
1057
|
+
target = filepath.Join(filepath.Dir(filename), target)
|
|
1058
|
+
}
|
|
1059
|
+
return safepath.ResolveExisting(target)
|
|
1060
|
+
}
|
|
1061
|
+
return safepath.ResolveExisting(filename)
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
func rootMutationTargets(data []byte, in harness.GuardInput) ([]string, bool) {
|
|
1065
|
+
switch {
|
|
1066
|
+
case isEditTool(in.ToolName):
|
|
1067
|
+
targets := mutationToolPaths(data, in)
|
|
1068
|
+
return targets, len(targets) == 0
|
|
1069
|
+
case isShellTool(in.ToolName):
|
|
1070
|
+
targets, opaque := wrightShellWritePaths(in.Command)
|
|
1071
|
+
if len(targets) > 0 {
|
|
1072
|
+
return targets, false
|
|
1073
|
+
}
|
|
1074
|
+
return nil, opaque && (reviewerMutateRe.MatchString(in.Command) || opaqueInterpreterRe.MatchString(in.Command))
|
|
1075
|
+
default:
|
|
1076
|
+
return nil, false
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
997
1080
|
func denyWright(h harness.Harness, stdout, stderr io.Writer, message string, reasonID drvreason.ID) int {
|
|
998
1081
|
out, err := h.PreToolDeny(message)
|
|
999
1082
|
if err != nil {
|
|
@@ -256,8 +256,6 @@ type strategyFlags struct {
|
|
|
256
256
|
values map[CandidateID]string
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
func (s *strategyFlags) String() string { return "" }
|
|
260
|
-
|
|
261
259
|
func (s *strategyFlags) Set(value string) error {
|
|
262
260
|
idText, text, ok := strings.Cut(value, "=")
|
|
263
261
|
id := CandidateID(idText)
|
|
@@ -66,7 +66,7 @@ var complianceMatrix = []Surface{
|
|
|
66
66
|
{"Stop gate", Conditional, Conditional, "all rest points", "observe-only finding + manual host-specific rite-status", "yes", "high", "Both deliver Stop; stop-gate blocks with DEVRITES_STOP_GATE=enforce or DEVRITES_HOOK_PROFILE=strict."},
|
|
67
67
|
{"SubagentStart discipline injection", Native, Native, "review/build fan-out", "agent file preamble", "yes", "high", "subagent-orient wired on both."},
|
|
68
68
|
{"Skill invocation", Native, Adapter, "all public rites", "explicit file read", "yes", "high", "Claude: native /rite. Codex: $rite over the mirrored .agents/skills tree."},
|
|
69
|
-
{"Reviewer subagent dispatch", Native,
|
|
69
|
+
{"Reviewer subagent dispatch", Native, Adapter, "review/seal confidence", "HITL stop; no root inline review", "yes", "high", "Codex V2 requires the exact named role and verifies its durable parent/child rollout because V2 collaboration lifecycle calls bypass hooks; V1 keeps the guarded explorer fallback."},
|
|
70
70
|
{"Standards step-0 load", Native, Instruction, "all phases", "explicit standards read", "partial", "medium", "Claude: skill Reads core.md. Codex: an AGENTS.md directive to read it."},
|
|
71
71
|
{"Project activation", Native, Conditional, "Codex-only startup", "inspect hooks/config, decide trust, then rerun doctor", "yes", "medium", "Codex silently skips every .codex/ layer in an untrusted project; trust remains an operator decision after inspection."},
|
|
72
72
|
}
|
|
@@ -199,6 +199,7 @@ type GuardInput struct {
|
|
|
199
199
|
ToolName string
|
|
200
200
|
Command string
|
|
201
201
|
FilePath string
|
|
202
|
+
SessionID string
|
|
202
203
|
AgentType string
|
|
203
204
|
AgentID string
|
|
204
205
|
ToolResponse string
|
|
@@ -209,6 +210,7 @@ type GuardInput struct {
|
|
|
209
210
|
// way the shell hooks' `JSON.stringify` node parse does.
|
|
210
211
|
func (h Harness) ParseGuardInput(r io.Reader) GuardInput {
|
|
211
212
|
var raw struct {
|
|
213
|
+
SessionID string `json:"session_id"`
|
|
212
214
|
ToolName string `json:"tool_name"`
|
|
213
215
|
ToolInput struct {
|
|
214
216
|
Command string `json:"command"`
|
|
@@ -237,6 +239,7 @@ func (h Harness) ParseGuardInput(r io.Reader) GuardInput {
|
|
|
237
239
|
ToolName: raw.ToolName,
|
|
238
240
|
Command: raw.ToolInput.Command,
|
|
239
241
|
FilePath: firstNonEmpty(raw.ToolInput.FilePath, raw.ToolInput.Path),
|
|
242
|
+
SessionID: raw.SessionID,
|
|
240
243
|
AgentType: raw.AgentType,
|
|
241
244
|
AgentID: raw.AgentID,
|
|
242
245
|
ToolResponse: resp,
|
|
@@ -22,7 +22,7 @@ import (
|
|
|
22
22
|
//
|
|
23
23
|
// base is the project directory that contains the .devrites tree.
|
|
24
24
|
//
|
|
25
|
-
// 0 doubt covered, or not assessable (no log / no wright
|
|
25
|
+
// 0 doubt covered, or not assessable (no log / no wright) → pass
|
|
26
26
|
// 1 wright dispatch(es) but ZERO doubt → verify against decisions.md
|
|
27
27
|
// 2 usage (no slug)
|
|
28
28
|
// 3 decisions.md records a stood decision with `doubt: MISSING`
|
|
@@ -39,23 +39,13 @@ func DoubtCoverage(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
39
39
|
dir := featureDir(root, slug)
|
|
40
40
|
|
|
41
41
|
// A recorded stood decision that was never doubted is the unambiguous
|
|
42
|
-
// failure: checked first, and on every path
|
|
43
|
-
// case below, where the footprint heuristic cannot apply).
|
|
42
|
+
// failure: checked first, and on every path.
|
|
44
43
|
if dec, ok := readFileOK(filepath.Join(dir, "decisions.md")); ok && strings.Contains(dec, "doubt: MISSING") {
|
|
45
44
|
fmt.Fprintln(stdout, "doubt-coverage: SKIPPED: a '## Decisions stood' entry in decisions.md has doubt: MISSING.")
|
|
46
45
|
fmt.Fprintln(stdout, "A decision was stood and recorded but never doubted. Re-dispatch doubt or escalate.")
|
|
47
46
|
return 3
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
// Inline build (Task tool unavailable): the wright + doubt ran in-context,
|
|
51
|
-
// dispatching nothing, so the footprint heuristic does not apply: the
|
|
52
|
-
// decisions.md check above is the assessment.
|
|
53
|
-
if isFile(filepath.Join(dir, ".reconcile-inline")) {
|
|
54
|
-
fmt.Fprintln(stdout, "doubt-coverage: inline build (.reconcile-inline): footprint heuristic n/a; no MISSING")
|
|
55
|
-
fmt.Fprintln(stdout, "verdict recorded. Verify by hand that each stood decision carries a devrites-doubt verdict.")
|
|
56
|
-
return 0
|
|
57
|
-
}
|
|
58
|
-
|
|
59
49
|
f, err := os.Open(filepath.Join(dir, "footprint.log"))
|
|
60
50
|
if err != nil {
|
|
61
51
|
fmt.Fprintln(stdout, "doubt-coverage: no footprint log: nothing to assess (pass)")
|
|
@@ -22,10 +22,7 @@ const (
|
|
|
22
22
|
reconcileAllowlistName = ".reconcile-allowlist"
|
|
23
23
|
reconcileCheckedName = ".reconcile-checked"
|
|
24
24
|
reconcileDevritesName = ".reconcile-devrites"
|
|
25
|
-
reconcileInlineName = ".reconcile-inline"
|
|
26
25
|
reconcileObjectsName = ".reconcile-objects"
|
|
27
|
-
reconcileObjectMarkerName = ".devrites-baseline"
|
|
28
|
-
reconcileLegacyClaimedName = ".reconcile-claimed"
|
|
29
26
|
defaultWrightAllowlistName = ".wright-allowlist"
|
|
30
27
|
wrightAllowlistFileEnv = "DEVRITES_WRIGHT_ALLOWLIST_FILE"
|
|
31
28
|
reconcileDevritesPathPrefix = ".devrites/"
|
|
@@ -63,14 +60,12 @@ func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
63
60
|
base := filepath.Join(d, reconcileBaseName)
|
|
64
61
|
capturedAllowlist := filepath.Join(d, reconcileAllowlistName)
|
|
65
62
|
devritesSnapshot := filepath.Join(d, reconcileDevritesName)
|
|
66
|
-
inline := filepath.Join(d, reconcileInlineName)
|
|
67
63
|
objects := filepath.Join(d, reconcileObjectsName)
|
|
68
|
-
legacyClaimed := filepath.Join(d, reconcileLegacyClaimedName)
|
|
69
64
|
checked := filepath.Join(d, reconcileCheckedName)
|
|
70
65
|
|
|
71
66
|
closeWindow := func() error {
|
|
72
67
|
var failures []string
|
|
73
|
-
for _, privateFile := range []string{base, capturedAllowlist, devritesSnapshot,
|
|
68
|
+
for _, privateFile := range []string{base, capturedAllowlist, devritesSnapshot, checked} {
|
|
74
69
|
if err := os.Remove(privateFile); err != nil && !os.IsNotExist(err) {
|
|
75
70
|
failures = append(failures, fmt.Sprintf("%s: %v", filepath.Base(privateFile), err))
|
|
76
71
|
}
|
|
@@ -136,7 +131,7 @@ func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
136
131
|
fmt.Fprintf(stderr, "reconcile: cannot refresh wright allowlist: %v\n", err)
|
|
137
132
|
return 6
|
|
138
133
|
}
|
|
139
|
-
state, err := captureReconcileDevritesState(root, devritesSnapshot, activeObjects, checked
|
|
134
|
+
state, err := captureReconcileDevritesState(root, devritesSnapshot, activeObjects, checked)
|
|
140
135
|
if err != nil {
|
|
141
136
|
fmt.Fprintf(stderr, "reconcile: cannot refresh .devrites snapshot: %v\n", err)
|
|
142
137
|
return 6
|
|
@@ -180,17 +175,12 @@ func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
180
175
|
fmt.Fprintf(stderr, "reconcile: cannot write snapshot: %v\n", err)
|
|
181
176
|
return 6
|
|
182
177
|
}
|
|
183
|
-
if err := os.WriteFile(filepath.Join(objects, reconcileObjectMarkerName), []byte(tree+"\n"), 0o600); err != nil {
|
|
184
|
-
_ = closeWindow()
|
|
185
|
-
fmt.Fprintf(stderr, "reconcile: cannot seal snapshot object database: %v\n", err)
|
|
186
|
-
return 6
|
|
187
|
-
}
|
|
188
178
|
if err := os.WriteFile(capturedAllowlist, renderWrightAllowlist(allowed), 0o600); err != nil {
|
|
189
179
|
_ = closeWindow()
|
|
190
180
|
fmt.Fprintf(stderr, "reconcile: cannot capture wright allowlist: %v\n", err)
|
|
191
181
|
return 6
|
|
192
182
|
}
|
|
193
|
-
state, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked
|
|
183
|
+
state, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked)
|
|
194
184
|
if err != nil {
|
|
195
185
|
_ = closeWindow()
|
|
196
186
|
fmt.Fprintf(stderr, "reconcile: cannot snapshot .devrites: %v\n", err)
|
|
@@ -225,7 +215,7 @@ func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
225
215
|
fmt.Fprintf(stderr, "reconcile: invalid .devrites snapshot: %v\n", err)
|
|
226
216
|
return 6
|
|
227
217
|
}
|
|
228
|
-
afterDevrites, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked
|
|
218
|
+
afterDevrites, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked)
|
|
229
219
|
if err != nil {
|
|
230
220
|
fmt.Fprintf(stderr, "reconcile: cannot compare .devrites: %v\n", err)
|
|
231
221
|
return 6
|
|
@@ -743,21 +733,6 @@ func loadReconcileBaseline(gitRoot, workspace string) (baseTree string, env []st
|
|
|
743
733
|
if baseTree == "" || strings.ContainsAny(baseTree, " \t\r\n") {
|
|
744
734
|
return "", nil, "", false, fmt.Errorf("%s does not contain one tree id", reconcileBaseName)
|
|
745
735
|
}
|
|
746
|
-
markerPath := filepath.Join(objectDir, reconcileObjectMarkerName)
|
|
747
|
-
markerInfo, err := os.Lstat(markerPath)
|
|
748
|
-
if err != nil {
|
|
749
|
-
return "", nil, "", false, fmt.Errorf("%s is not a sealed object database: %w", reconcileObjectsName, err)
|
|
750
|
-
}
|
|
751
|
-
if !markerInfo.Mode().IsRegular() {
|
|
752
|
-
return "", nil, "", false, fmt.Errorf("%s marker is not a regular file", reconcileObjectsName)
|
|
753
|
-
}
|
|
754
|
-
marker, err := os.ReadFile(markerPath)
|
|
755
|
-
if err != nil {
|
|
756
|
-
return "", nil, "", false, fmt.Errorf("read %s marker: %w", reconcileObjectsName, err)
|
|
757
|
-
}
|
|
758
|
-
if strings.TrimSpace(string(marker)) != baseTree {
|
|
759
|
-
return "", nil, "", false, fmt.Errorf("%s marker does not match %s", reconcileObjectsName, reconcileBaseName)
|
|
760
|
-
}
|
|
761
736
|
env, err = reconcileGitEnv(gitRoot, objectDir)
|
|
762
737
|
if err != nil {
|
|
763
738
|
return "", nil, "", false, err
|
|
@@ -207,9 +207,8 @@ func TestReconcileCheckFlagsUnauthorizedChange(t *testing.T) {
|
|
|
207
207
|
if code, out := runReconcile(t, root, "snapshot", "feat"); code != 0 {
|
|
208
208
|
t.Fatalf("snapshot = %d, want 0\n%s", code, out)
|
|
209
209
|
}
|
|
210
|
-
// A writer cannot
|
|
210
|
+
// A writer cannot change a path that was not authorized at dispatch.
|
|
211
211
|
writeFile(t, filepath.Join(gitRoot, "rogue.go"), "package main\n")
|
|
212
|
-
writeFile(t, filepath.Join(root, "work", "feat", reconcileLegacyClaimedName), "rogue.go\n")
|
|
213
212
|
|
|
214
213
|
code, out := runReconcile(t, root, "check", "feat")
|
|
215
214
|
if code != 5 {
|
|
@@ -383,14 +382,20 @@ func TestReconcileCheckFailsClosedWhenObjectDatabaseIsMissing(t *testing.T) {
|
|
|
383
382
|
}
|
|
384
383
|
|
|
385
384
|
func TestReconcileCheckFailsClosedWhenObjectDatabaseIsCorrupt(t *testing.T) {
|
|
386
|
-
newGitRepo(t)
|
|
385
|
+
gitRoot := newGitRepo(t)
|
|
387
386
|
root := workspace(t, "feat")
|
|
388
387
|
writeWrightAllowlist(t, root, "feat", "seed.go")
|
|
388
|
+
writeFile(t, filepath.Join(gitRoot, "seed.go"), "package seed\n")
|
|
389
389
|
if code, out := runReconcile(t, root, "snapshot", "feat"); code != 0 {
|
|
390
390
|
t.Fatalf("snapshot = %d, want 0\n%s", code, out)
|
|
391
391
|
}
|
|
392
|
-
|
|
393
|
-
if err
|
|
392
|
+
baseData, err := os.ReadFile(filepath.Join(featureDir(root, "feat"), reconcileBaseName))
|
|
393
|
+
if err != nil {
|
|
394
|
+
t.Fatal(err)
|
|
395
|
+
}
|
|
396
|
+
baseTree := strings.TrimSpace(string(baseData))
|
|
397
|
+
treeObject := filepath.Join(featureDir(root, "feat"), reconcileObjectsName, baseTree[:2], baseTree[2:])
|
|
398
|
+
if err := os.Remove(treeObject); err != nil {
|
|
394
399
|
t.Fatal(err)
|
|
395
400
|
}
|
|
396
401
|
|
|
@@ -398,7 +403,7 @@ func TestReconcileCheckFailsClosedWhenObjectDatabaseIsCorrupt(t *testing.T) {
|
|
|
398
403
|
if code != 6 {
|
|
399
404
|
t.Fatalf("check = %d, want 6\n%s", code, out)
|
|
400
405
|
}
|
|
401
|
-
if !strings.Contains(out, "
|
|
406
|
+
if !strings.Contains(out, "tree is unavailable") {
|
|
402
407
|
t.Fatalf("missing corrupt-object diagnostic:\n%s", out)
|
|
403
408
|
}
|
|
404
409
|
}
|
|
@@ -502,23 +507,22 @@ func TestReconcileRefreshRequiresPriorCleanCheck(t *testing.T) {
|
|
|
502
507
|
}
|
|
503
508
|
}
|
|
504
509
|
|
|
505
|
-
func
|
|
510
|
+
func TestReconcileCloseClearsPrivateWindowState(t *testing.T) {
|
|
506
511
|
newGitRepo(t)
|
|
507
512
|
root := workspace(t, "feat")
|
|
508
513
|
writeFile(t, filepath.Join(root, "work", "feat", reconcileBaseName), "stale\n")
|
|
509
|
-
writeFile(t, filepath.Join(root, "work", "feat", reconcileInlineName), "")
|
|
510
514
|
if err := os.MkdirAll(filepath.Join(root, "work", "feat", reconcileObjectsName), 0o755); err != nil {
|
|
511
515
|
t.Fatal(err)
|
|
512
516
|
}
|
|
513
517
|
|
|
514
518
|
code, out := runReconcile(t, root, "close", "feat")
|
|
515
519
|
if code != 0 {
|
|
516
|
-
t.Fatalf("
|
|
520
|
+
t.Fatalf("close = %d, want 0\n%s", code, out)
|
|
517
521
|
}
|
|
518
522
|
if !strings.Contains(out, "closed slice window") {
|
|
519
523
|
t.Errorf("expected close message:\n%s", out)
|
|
520
524
|
}
|
|
521
525
|
if isFile(filepath.Join(featureDir(root, "feat"), reconcileBaseName)) || isDir(filepath.Join(featureDir(root, "feat"), reconcileObjectsName)) {
|
|
522
|
-
t.Fatal("
|
|
526
|
+
t.Fatal("close retained private baseline state")
|
|
523
527
|
}
|
|
524
528
|
}
|
package/engine/main.go
CHANGED
|
@@ -93,7 +93,8 @@ Hooks:
|
|
|
93
93
|
hook orient Emit SessionStart orientation for the active feature
|
|
94
94
|
hook stop-gate Refuse to end a turn at a provably inconsistent rest point
|
|
95
95
|
hook reviewer-readonly Deny every mutation/dispatch surface for read-only DevRites agents
|
|
96
|
-
hook
|
|
96
|
+
hook agent-dispatch Bind Codex spawn, start, wait, result, and reconcile receipts
|
|
97
|
+
hook subagent-orient Inject DevRites discipline and bind a spawned specialist role
|
|
97
98
|
hook cursor Re-inject the active feature's cursor (UserPromptSubmit)
|
|
98
99
|
hook statusline One-line workspace HUD (settings.json statusLine)
|
|
99
100
|
hook redwatch Fail-on-red sentinel: set/clear .red after a test/build command
|
|
@@ -510,6 +511,8 @@ func cmdHook(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|
|
510
511
|
return hookStopGate(h, stdin, stdout, stderr)
|
|
511
512
|
case "reviewer-readonly":
|
|
512
513
|
return hookReviewerReadonly(h, stdin, stdout, stderr)
|
|
514
|
+
case "agent-dispatch":
|
|
515
|
+
return hookAgentDispatch(h, stdin, stdout, stderr)
|
|
513
516
|
case "subagent-orient":
|
|
514
517
|
return hookSubagentOrient(h, stdin, stdout, stderr)
|
|
515
518
|
case "cursor":
|