Skip to content
Open
21 changes: 21 additions & 0 deletions internal/assets/claude/agents/jd-fix-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: jd-fix-agent
description: >
Surgical fix agent for judgment-day protocol. Applies only confirmed fixes
from the verdict synthesis. Triggered by the orchestrator after judges agree on issues.
model: {{CLAUDE_MODEL}}
tools: Read, Edit, Write, Glob, Grep, Bash, mcp__plugin_engram_engram__mem_search, mcp__plugin_engram_engram__mem_get_observation, mcp__plugin_engram_engram__mem_save, mcp__plugin_engram_engram__mem_update
---

You are a judgment-day surgical fix agent. Execute the fix instructions
provided in the delegate prompt exactly.

## Rules
- Do NOT use the Task/Agent tool. Do NOT delegate further.
- Fix ONLY the confirmed issues listed in the delegate prompt.
- Do NOT refactor beyond what is strictly needed to fix each issue.
- Do NOT change code that was not flagged.
- After each fix, note: file changed, line changed, what was done.
- **Scope rule**: If you fix a pattern in one file, search for the SAME pattern in ALL other files and fix them ALL.
- Return a summary: ## Fixes Applied - [file:line] β€” {what was fixed}
- At the end, include: **Skill Resolution**: {injected|fallback-registry|fallback-path|none} β€” {details}
19 changes: 19 additions & 0 deletions internal/assets/claude/agents/jd-judge-a.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: jd-judge-a
description: >
Adversarial code reviewer β€” blind judge A for judgment-day parallel review protocol.
Triggered by the orchestrator when judgment-day is invoked. Reviews code for
correctness, edge cases, security, performance, and project standards.
model: {{CLAUDE_MODEL}}
tools: Read, Glob, Grep, Bash, mcp__plugin_engram_engram__mem_search, mcp__plugin_engram_engram__mem_get_observation
---

You are a judgment-day adversarial reviewer (Judge A). Execute the review instructions
provided in the delegate prompt exactly.

## Rules
- Do NOT use the Task/Agent tool. Do NOT delegate further.
- Do NOT modify any code β€” your job is ONLY to find problems.
- Be thorough and adversarial. Assume the code has bugs until proven otherwise.
- Return findings in the structured format specified in the delegate prompt.
- At the end, include: **Skill Resolution**: {injected|fallback-registry|fallback-path|none} β€” {details}
19 changes: 19 additions & 0 deletions internal/assets/claude/agents/jd-judge-b.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: jd-judge-b
description: >
Adversarial code reviewer β€” blind judge B for judgment-day parallel review protocol.
Triggered by the orchestrator when judgment-day is invoked. Reviews code for
correctness, edge cases, security, performance, and project standards.
model: {{CLAUDE_MODEL}}
tools: Read, Glob, Grep, Bash, mcp__plugin_engram_engram__mem_search, mcp__plugin_engram_engram__mem_get_observation
---

You are a judgment-day adversarial reviewer (Judge B). Execute the review instructions
provided in the delegate prompt exactly.

## Rules
- Do NOT use the Task/Agent tool. Do NOT delegate further.
- Do NOT modify any code β€” your job is ONLY to find problems.
- Be thorough and adversarial. Assume the code has bugs until proven otherwise.
- Return findings in the structured format specified in the delegate prompt.
- At the end, include: **Skill Resolution**: {injected|fallback-registry|fallback-path|none} β€” {details}
12 changes: 12 additions & 0 deletions internal/assets/kiro/agents/jd-fix-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: jd-fix-agent
description: >
Surgical fix agent for judgment-day protocol. Applies only confirmed fixes.
model: {{KIRO_MODEL}}
tools: ["@builtin", "@engram"]
includeMcpJson: true
---

You are a judgment-day surgical fix agent. Execute the fix instructions provided
in the delegate prompt exactly. Do NOT delegate further. Fix ONLY the confirmed
issues listed. Do NOT refactor beyond what is strictly needed.
12 changes: 12 additions & 0 deletions internal/assets/kiro/agents/jd-judge-a.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: jd-judge-a
description: >
Adversarial code reviewer β€” blind judge A for judgment-day parallel review protocol.
model: {{KIRO_MODEL}}
tools: ["@builtin", "@engram"]
includeMcpJson: true
---

You are a judgment-day adversarial reviewer (Judge A). Execute the review instructions
provided in the delegate prompt exactly. Do NOT delegate further. Do NOT modify any code.
Be thorough and adversarial. Return findings in the structured format specified.
12 changes: 12 additions & 0 deletions internal/assets/kiro/agents/jd-judge-b.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: jd-judge-b
description: >
Adversarial code reviewer β€” blind judge B for judgment-day parallel review protocol.
model: {{KIRO_MODEL}}
tools: ["@builtin", "@engram"]
includeMcpJson: true
---

You are a judgment-day adversarial reviewer (Judge B). Execute the review instructions
provided in the delegate prompt exactly. Do NOT delegate further. Do NOT modify any code.
Be thorough and adversarial. Return findings in the structured format specified.
26 changes: 25 additions & 1 deletion internal/assets/opencode/sdd-overlay-multi.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
"sdd-apply": "allow",
"sdd-verify": "allow",
"sdd-archive": "allow",
"sdd-onboard": "allow"
"sdd-onboard": "allow",
"jd-judge-a": "allow",
"jd-judge-b": "allow",
"jd-fix-agent": "allow"
}
}
},
Expand Down Expand Up @@ -150,6 +153,27 @@
"edit": true,
"bash": true
}
},
"jd-judge-a": {
"mode": "subagent",
"hidden": true,
"description": "Adversarial code reviewer β€” blind judge A for judgment-day protocol",
"prompt": "You are a judgment-day adversarial reviewer. Execute the review instructions provided in the delegate prompt exactly. Do NOT delegate further. Do NOT modify any code β€” your job is ONLY to find problems.",
"tools": { "read": true, "glob": true, "grep": true, "bash": true }
},
"jd-judge-b": {
"mode": "subagent",
"hidden": true,
"description": "Adversarial code reviewer β€” blind judge B for judgment-day protocol",
"prompt": "You are a judgment-day adversarial reviewer. Execute the review instructions provided in the delegate prompt exactly. Do NOT delegate further. Do NOT modify any code β€” your job is ONLY to find problems.",
"tools": { "read": true, "glob": true, "grep": true, "bash": true }
},
"jd-fix-agent": {
"mode": "subagent",
"hidden": true,
"description": "Surgical fix agent for judgment-day protocol",
"prompt": "You are a judgment-day surgical fix agent. Execute the fix instructions provided in the delegate prompt exactly. Do NOT delegate further. Fix ONLY the confirmed issues listed β€” do NOT refactor beyond what is strictly needed.",
"tools": { "read": true, "write": true, "edit": true, "bash": true }
}
}
}
25 changes: 18 additions & 7 deletions internal/assets/skills/judgment-day/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,22 @@ This ensures judges review against project-specific standards, not just generic

### Pattern 1: Parallel Blind Review

- Launch **TWO** sub-agents via `delegate` (async, parallel β€” never sequential)
- Each agent receives the **same target** but works **independently**
- **Neither agent knows about the other** β€” no cross-contamination
- Both use identical review criteria but may find different issues
- NEVER do the review yourself as the orchestrator β€” your job is coordination only
Launch **TWO** sub-agents in parallel (never sequential).
Each agent receives the **same target** but works **independently**.
**Neither agent knows about the other** β€” no cross-contamination.
Both use identical review criteria but may find different issues.
NEVER do the review yourself as the orchestrator β€” your job is coordination only.

**For agents with named sub-agent support (OpenCode multi-mode)**:
- Launch Judge A via `delegate(agent="jd-judge-a", prompt="...")`
- Launch Judge B via `delegate(agent="jd-judge-b", prompt="...")`
- Each judge uses its configured model from the Model Assignments table.
- Launch the Fix Agent via `delegate(agent="jd-fix-agent", prompt="...")`

**For agents WITHOUT named sub-agent support (Claude Code, Cursor, Windsurf, Gemini, Codex, etc.)**:
- Launch TWO delegates via `delegate()` (async, parallel) with full inline prompts.
- The model is controlled by the agent's native model switching mechanism.
- Pass the model alias from the Model Assignments table if the agent supports per-call model parameters.

### Pattern 2: Verdict Synthesis

Expand Down Expand Up @@ -76,7 +87,7 @@ WARNING (theoretical) β†’ Requires a contrived scenario, corrupted input, or con

### Pattern 4: Fix and Re-judge

1. If **confirmed CRITICALs or real WARNINGs** exist β†’ delegate a **Fix Agent** (separate delegation)
1. If **confirmed CRITICALs or real WARNINGs** exist β†’ delegate a **Fix Agent** (via `delegate(agent="jd-fix-agent", ...)` for OpenCode, or generic `delegate()` for other agents)
2. After Fix Agent completes β†’ re-launch **both judges in parallel** (same blind protocol, fresh delegates)
3. **After 2 fix iterations**, if issues remain β†’ present findings to user and ASK: "ΒΏQuerΓ©s que siga iterando? / Should I continue iterating?" If YES β†’ continue fix+judge cycle. If NO β†’ JUDGMENT: ESCALATED.
4. If both judges return clean β†’ JUDGMENT: APPROVED βœ…
Expand Down Expand Up @@ -332,7 +343,7 @@ Before pushing, committing, summarizing, or telling the user "done":
## Rules

- The **orchestrator NEVER reviews code itself** β€” it only launches judges, reads results, and synthesizes
- Judges MUST be launched as `delegate` (async) so they run in **parallel**
- Judges MUST be launched via `delegate` (async) so they run in **parallel** β€” use named agents (`jd-judge-a`, `jd-judge-b`) when available, or generic `delegate()` otherwise
- The **Fix Agent is a separate delegation** β€” never use one of the judges as the fixer
- If user provides **custom review criteria**, include them in BOTH judge prompts (identical)
- If target scope is **unclear**, stop and ask before launching β€” partial reviews are useless
Expand Down
27 changes: 25 additions & 2 deletions internal/components/sdd/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/gentleman-programming/gentle-ai/internal/assets"
"github.com/gentleman-programming/gentle-ai/internal/components/filemerge"
"github.com/gentleman-programming/gentle-ai/internal/model"
"github.com/gentleman-programming/gentle-ai/internal/opencode"
)

type InjectionResult struct {
Expand Down Expand Up @@ -1278,6 +1279,9 @@ var claudeModelAssignmentRowOrder = []string{
"sdd-apply",
"sdd-verify",
"sdd-archive",
"jd-judge-a",
"jd-judge-b",
"jd-fix-agent",
"default",
}

Expand All @@ -1291,6 +1295,9 @@ var claudeModelAssignmentReasons = map[string]string{
"sdd-apply": "Implementation",
"sdd-verify": "Validation against spec",
"sdd-archive": "Copy and close",
"jd-judge-a": "Adversarial review β€” blind judge A",
"jd-judge-b": "Adversarial review β€” blind judge B",
"jd-fix-agent": "Surgical fixes from confirmed issues",
"default": "Non-SDD general delegation",
}

Expand Down Expand Up @@ -1350,6 +1357,18 @@ func renderClaudeModelAssignmentsSection(assignments map[string]model.ClaudeMode
return b.String()
}

// isJDAgent reports whether the agent name is a judgment-day workflow agent.
// JD agents are excluded from root model fallback to preserve independent
// model configuration for diversity of perspective between judges.
func isJDAgent(name string) bool {
for _, jd := range opencode.JDPhases() {
if name == jd {
return true
}
}
return false
}

// injectModelAssignments injects "model" fields into sub-agent definitions
// within the overlay JSON before it is merged into the settings file.
//
Expand Down Expand Up @@ -1393,8 +1412,12 @@ func injectModelAssignments(overlayBytes []byte, assignments map[string]model.Mo
// 2. Agent already exists in user's config β€” let merge preserve whatever they have
// (don't touch the overlay for this agent's model)
case rootModelID != "":
// 3. Fresh install or new agent: use root model as default to break inheritance
agentMap["model"] = rootModelID
// 3. Fresh install or new agent: use root model as default to break inheritance.
// Exception: JD agents are excluded from root model propagation to support
// independent model configuration and diversity of perspective between judges.
if !isJDAgent(phase) {
agentMap["model"] = rootModelID
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions internal/components/sdd/inject_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -955,9 +955,9 @@ func TestInjectOpenCodeMultiMode(t *testing.T) {
t.Fatalf("agent key has unexpected type: %T", agentRaw)
}

// Multi overlay must contain orchestrator + 10 sub-agents = 11 agents.
if len(agentMap) != 11 {
t.Fatalf("agent count = %d, want 11", len(agentMap))
// Multi overlay must contain orchestrator + 10 sub-agents + 3 JD agents = 14 agents.
if len(agentMap) != 14 {
t.Fatalf("agent count = %d, want 14", len(agentMap))
}

// Verify orchestrator is present.
Expand Down
9 changes: 9 additions & 0 deletions internal/components/sdd/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/gentleman-programming/gentle-ai/internal/assets"
"github.com/gentleman-programming/gentle-ai/internal/components/filemerge"
"github.com/gentleman-programming/gentle-ai/internal/model"
"github.com/gentleman-programming/gentle-ai/internal/opencode"
)

// profileNameRegex matches valid profile name slugs: lowercase alphanumeric + hyphens,
Expand All @@ -22,6 +23,9 @@ var profileNameRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`)
var reservedProfileNames = map[string]bool{
"default": true,
"sdd-orchestrator": true,
"jd-judge-a": true,
"jd-judge-b": true,
"jd-fix-agent": true,
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JD agent names are duplicated as string literals in reservedProfileNames. Since there is already a central list in opencode.JDPhases(), consider building this map using that list (plus the existing reserved names) to avoid future drift if JD agents are renamed/added.

Suggested change
var reservedProfileNames = map[string]bool{
"default": true,
"sdd-orchestrator": true,
"jd-judge-a": true,
"jd-judge-b": true,
"jd-fix-agent": true,
}
// Keep explicitly reserved non-JD names here, and derive JD agent names from
// the canonical opencode.JDPhases() list to avoid drift.
var reservedProfileNames = func() map[string]bool {
names := map[string]bool{
"default": true,
"sdd-orchestrator": true,
}
for _, name := range opencode.JDPhases() {
names[name] = true
}
return names
}()

Copilot uses AI. Check for mistakes.

// ValidateProfileName returns an error if the profile name is not a valid
Expand Down Expand Up @@ -253,6 +257,11 @@ func GenerateProfileOverlay(profile model.Profile, homeDir string) ([]byte, erro
for _, phase := range profilePhaseOrder {
taskPerms[phase+suffix] = "allow"
}
// Add JD agent permissions (global, not profile-scoped).
// JD agents are workflow-level and shared across profiles.
for _, jd := range opencode.JDPhases() {
taskPerms[jd] = "allow"
}

orchEntry := map[string]any{
"mode": "primary",
Expand Down
19 changes: 16 additions & 3 deletions internal/components/sdd/profiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/gentleman-programming/gentle-ai/internal/assets"
"github.com/gentleman-programming/gentle-ai/internal/model"
"github.com/gentleman-programming/gentle-ai/internal/opencode"
)

func TestResolveProfileStrategy_ExplicitWins(t *testing.T) {
Expand Down Expand Up @@ -471,9 +472,10 @@ func TestDefaultOverlayTaskPermissions_ExplicitAllowlist(t *testing.T) {
tests := []struct {
name string
assetPath string
includeJD bool // multi overlay includes JD agents; single does not
}{
{name: "single", assetPath: "opencode/sdd-overlay-single.json"},
{name: "multi", assetPath: "opencode/sdd-overlay-multi.json"},
{name: "single", assetPath: "opencode/sdd-overlay-single.json", includeJD: false},
{name: "multi", assetPath: "opencode/sdd-overlay-multi.json", includeJD: true},
}

for _, tt := range tests {
Expand All @@ -493,7 +495,14 @@ func TestDefaultOverlayTaskPermissions_ExplicitAllowlist(t *testing.T) {
t.Fatal("task block must use __replace__ sentinel to discard stale wildcards on sync")
}

assertExactTaskPermissions(t, taskMap, expectedTaskPermissions(""))
expected := expectedTaskPermissions("")
if !tt.includeJD {
// Single overlay does not include JD agent permissions.
for _, jd := range opencode.JDPhases() {
delete(expected, jd)
}
}
assertExactTaskPermissions(t, taskMap, expected)
})
}
}
Expand Down Expand Up @@ -741,6 +750,10 @@ func expectedTaskPermissions(suffix string) map[string]any {
for _, phase := range profilePhaseOrder {
permissions[phase+suffix] = "allow"
}
// JD agents are global (not profile-scoped) β€” always unsuffixed.
for _, jd := range opencode.JDPhases() {
permissions[jd] = "allow"
}
return permissions
}

Expand Down
18 changes: 9 additions & 9 deletions internal/components/sdd/read_assignments.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import (
"github.com/gentleman-programming/gentle-ai/internal/opencode"
)

// sddPhaseSet is the set of valid SDD phase agent names that may appear in
// opencode.json. It includes the sub-agent phases plus the orchestrator.
var sddPhaseSet = buildSDDPhaseSet()
// sddPhaseSet is the set of valid agent names that may appear in
// opencode.json. It includes SDD phases, JD agents, and the orchestrator.
var sddPhaseSet = buildConfigurableAgentSet()

func buildSDDPhaseSet() map[string]bool {
phases := opencode.SDDPhases()
func buildConfigurableAgentSet() map[string]bool {
phases := opencode.ConfigurableAgentPhases() // SDD + JD phases
set := make(map[string]bool, len(phases)+1)
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name sddPhaseSet is now misleading: it includes JD agents and the orchestrator (i.e., more than SDD phases). Renaming it (e.g., configurableAgentSet / agentNameSet) would reduce confusion when reading the filtering logic in ReadCurrentModelAssignments.

Copilot uses AI. Check for mistakes.
for _, p := range phases {
set[p] = true
Expand All @@ -31,11 +31,11 @@ func ReadCurrentProfiles(settingsPath string) ([]model.Profile, error) {
}

// ReadCurrentModelAssignments reads the agent definitions from opencode.json
// at settingsPath and extracts the "model" field for each SDD phase agent.
// at settingsPath and extracts the "model" field for each configurable agent.
//
// Only agents whose names match an SDD phase (from opencode.SDDPhases()) or
// "sdd-orchestrator" are included. Agents without a "model" field, or with a
// malformed model value (not in "provider:model-id" format), are silently
// Only agents whose names match a configurable agent phase (SDD phases, JD agents
// via opencode.ConfigurableAgentPhases()) or "sdd-orchestrator" are included.
// Agents without a "model" field, or with a malformed model value, are silently
// skipped.
//
// Returns an empty map (no error) when the file does not exist, contains no
Expand Down
3 changes: 3 additions & 0 deletions internal/components/uninstall/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ var (
"sdd-verify",
"sdd-archive",
"sdd-onboard",
"jd-judge-a",
"jd-judge-b",
"jd-fix-agent",
}
sddSkillPhaseIDs = sddPhaseAgents[1:]
)
Expand Down
Loading
Loading