From 224205189a8f3d9bd5a17f25bc91aed930f1a7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20D=2E?= <9351115+decode2@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:06:58 -0300 Subject: [PATCH] fix(opencode): use relative SDD prompt paths --- internal/cli/sync_test.go | 16 ++----- internal/components/golden_test.go | 9 +--- internal/components/sdd/inject.go | 11 +++-- internal/components/sdd/inject_test.go | 28 +++++++++-- internal/components/sdd/profiles.go | 8 ++-- .../components/sdd/profiles_lifecycle_test.go | 8 ++-- internal/components/sdd/profiles_test.go | 31 +++++++----- internal/components/sdd/prompts.go | 15 ++++++ internal/components/sdd/prompts_test.go | 48 +++++++++++++++++-- .../golden/sdd-opencode-multi-settings.golden | 20 ++++---- 10 files changed, 133 insertions(+), 61 deletions(-) diff --git a/internal/cli/sync_test.go b/internal/cli/sync_test.go index 21bf760c0..3db4f8fa5 100644 --- a/internal/cli/sync_test.go +++ b/internal/cli/sync_test.go @@ -1988,30 +1988,24 @@ func TestRunSyncWithProfilesIntegration(t *testing.T) { t.Errorf("opencode.json should contain balanced orchestrator model 'claude-sonnet-4-5'") } - // Verify prompt files exist in ~/.config/opencode/prompts/sdd/. + // Verify prompt references are relative to the generated OpenCode settings. // Note: prompt files are written only for multi-mode. For single-mode syncs, // profile sub-agents use {file:...} references that rely on prompts being written // during a prior multi-mode sync. Check that the profile overlay is written correctly // by verifying the agent keys themselves are present (already done above). - // The prompt directory is populated by the profile generator which calls - // SharedPromptDir internally — verify the directory path is referenced correctly. - promptDir := filepath.Join(home, ".config", "opencode", "prompts", "sdd") promptPhases := []string{ "sdd-init", "sdd-explore", "sdd-propose", "sdd-spec", "sdd-design", "sdd-tasks", "sdd-apply", "sdd-verify", "sdd-archive", "sdd-onboard", } - // Verify the opencode.json file references mention the correct prompt directory. - slashPromptDir := filepath.ToSlash(promptDir) - if !strings.Contains(settingsStr, slashPromptDir) { - t.Errorf("opencode.json should reference prompt directory %q", slashPromptDir) - } - // Verify all phase prompt file references appear in the settings. for _, phase := range promptPhases { - promptRef := filepath.ToSlash(filepath.Join(promptDir, phase+".md")) + promptRef := "{file:./prompts/sdd/" + phase + ".md}" if !strings.Contains(settingsStr, promptRef) { t.Errorf("opencode.json should contain prompt file reference for %q", promptRef) } } + if slashHome := filepath.ToSlash(home); strings.Contains(settingsStr, slashHome) { + t.Errorf("opencode.json should not contain temp home path %q", slashHome) + } // Run 2: same selection → all assets already current → filesChanged=0. // Note: The second sync with profiles will re-generate the overlay, but since diff --git a/internal/components/golden_test.go b/internal/components/golden_test.go index 7617f4df3..c7e64502d 100644 --- a/internal/components/golden_test.go +++ b/internal/components/golden_test.go @@ -175,14 +175,7 @@ func TestGoldenSDD_OpenCode_Multi(t *testing.T) { t.Fatalf("multi-mode settings missing orchestrator tool %s", toolName) } } - // Normalize the absolute home path in the settings JSON so the golden - // file remains stable across test runs (temp dirs change each run). - // Sub-agent prompts now use {file:/abs/path/...} references. - jsonStr := string(settingsJSON) - jsonStr = strings.ReplaceAll(jsonStr, home, "{{HOME}}") - jsonStr = strings.ReplaceAll(jsonStr, filepath.ToSlash(home), "{{HOME}}") - normalizedSettings := []byte(jsonStr) - assertGolden(t, "sdd-opencode-multi-settings.golden", normalizedSettings) + assertGolden(t, "sdd-opencode-multi-settings.golden", settingsJSON) legacyPluginPath := filepath.Join(home, ".config", "opencode", "plugins", "background-agents.ts") if _, err := os.Stat(legacyPluginPath); !os.IsNotExist(err) { diff --git a/internal/components/sdd/inject.go b/internal/components/sdd/inject.go index 735048cf9..5934a958f 100644 --- a/internal/components/sdd/inject.go +++ b/internal/components/sdd/inject.go @@ -539,7 +539,7 @@ func Inject(homeDir string, adapter agents.Adapter, sddMode model.SDDModeID, opt return InjectionResult{}, fmt.Errorf("clean stale profile JD agents %q: %w", profile.Name, cleanupErr) } changed = changed || cleanupResult.Changed - profileOverlay, profileErr := GenerateProfileOverlay(profile, homeDir, opts.CodeGraphGuidanceMarkdown) + profileOverlay, profileErr := GenerateProfileOverlay(profile, homeDir, settingsPath, opts.CodeGraphGuidanceMarkdown) if profileErr != nil { return InjectionResult{}, fmt.Errorf("generate profile overlay %q: %w", profile.Name, profileErr) } @@ -913,10 +913,9 @@ func inlineOpenCodeSDDPrompts(overlayBytes []byte, homeDir, settingsPath string, } } - // Replace sub-agent prompt placeholders with {file:} references. + // Replace sub-agent prompt placeholders with settings-relative file references. // The placeholder format is __PROMPT_FILE_{phase}__ where {phase} is the agent name. if homeDir != "" { - promptDir := SharedPromptDir(homeDir) for _, phase := range subAgentPhaseOrder { agentRaw, exists := agentsMap[phase] if !exists { @@ -928,7 +927,11 @@ func inlineOpenCodeSDDPrompts(overlayBytes []byte, homeDir, settingsPath string, } placeholder := "__PROMPT_FILE_" + phase + "__" if prompt, _ := agentMap["prompt"].(string); prompt == placeholder { - agentMap["prompt"] = "{file:" + filepath.ToSlash(filepath.Join(promptDir, phase+".md")) + "}" + promptRef, err := SharedPromptFileRef(settingsPath, homeDir, phase) + if err != nil { + return nil, fmt.Errorf("build shared prompt file reference for %q: %w", phase, err) + } + agentMap["prompt"] = promptRef } } } diff --git a/internal/components/sdd/inject_test.go b/internal/components/sdd/inject_test.go index 32c127d27..ed86b47b7 100644 --- a/internal/components/sdd/inject_test.go +++ b/internal/components/sdd/inject_test.go @@ -2339,13 +2339,17 @@ func TestInjectOpenCodeSubagentPromptsStayExecutorScoped(t *testing.T) { t.Fatalf("%s has unexpected type: %T", phase, raw) } - // After the shared-prompt-files refactor, the prompt field is a {file:...} - // reference. The executor-scoped content lives in the prompt file on disk. prompt, _ := agentDef["prompt"].(string) - expectedRef := "{file:" + filepath.ToSlash(filepath.Join(promptDir, phase+".md")) + "}" + expectedRef, err := SharedPromptFileRef(settingsPath, home, phase) + if err != nil { + t.Fatalf("SharedPromptFileRef() error = %v", err) + } if prompt != expectedRef { t.Fatalf("%s prompt = %q, want {file:...} reference %q", phase, prompt, expectedRef) } + if strings.Contains(prompt, filepath.ToSlash(home)) { + t.Fatalf("%s prompt = %q, contains home path", phase, prompt) + } // Also verify the prompt file contains the executor-scoped content // (skill content that makes clear this is the executor, not orchestrator). @@ -2368,6 +2372,24 @@ func TestInjectOpenCodeSubagentPromptsStayExecutorScoped(t *testing.T) { } } +func TestInjectKilocodeSubagentPromptUsesSharedRelativePath(t *testing.T) { + home := t.TempDir() + adapter := kilocodeAdapter() + + if _, err := Inject(home, adapter, "multi"); err != nil { + t.Fatalf("Inject(multi) error = %v", err) + } + + prompt := agentPrompt(t, readOpenCodeAgents(t, adapter.SettingsPath(home)), "sdd-apply") + want := "{file:../opencode/prompts/sdd/sdd-apply.md}" + if prompt != want { + t.Fatalf("sdd-apply prompt = %q, want %q", prompt, want) + } + if strings.Contains(prompt, filepath.ToSlash(home)) { + t.Fatalf("sdd-apply prompt = %q, contains home path", prompt) + } +} + func TestInjectOpenCodeEmptySDDModeDefaultsSingle(t *testing.T) { mockNoPackageManager(t) home := t.TempDir() diff --git a/internal/components/sdd/profiles.go b/internal/components/sdd/profiles.go index 131e4b72f..f8cddfd24 100644 --- a/internal/components/sdd/profiles.go +++ b/internal/components/sdd/profiles.go @@ -243,7 +243,7 @@ func extractModelFromAgent(agentMap map[string]any) model.ModelAssignment { // sub-agent references and model assignments table), permissions scoped to *-{name} // - sdd-{phase}-{name} (10 agents): subagent mode, hidden, file reference to // the shared prompt at SharedPromptDir(homeDir)/sdd-{phase}.md -func GenerateProfileOverlay(profile model.Profile, homeDir string, codeGraphGuidance ...string) ([]byte, error) { +func GenerateProfileOverlay(profile model.Profile, homeDir, settingsPath string, codeGraphGuidance ...string) ([]byte, error) { if profile.Name == "" || profile.Name == "default" { return nil, fmt.Errorf("GenerateProfileOverlay: profile name must be non-empty and not 'default'") } @@ -325,7 +325,6 @@ func GenerateProfileOverlay(profile model.Profile, homeDir string, codeGraphGuid agentMap[orchestratorKey] = orchEntry // Sub-agent entries - promptDir := SharedPromptDir(homeDir) phaseDescriptions := map[string]string{ "sdd-init": "Bootstrap SDD context and project configuration", "sdd-explore": "Investigate codebase and think through ideas", @@ -341,7 +340,10 @@ func GenerateProfileOverlay(profile model.Profile, homeDir string, codeGraphGuid for _, phase := range profilePhaseOrder { key := phase + suffix - prompt := "{file:" + filepath.ToSlash(filepath.Join(promptDir, phase+".md")) + "}" + prompt, err := SharedPromptFileRef(settingsPath, homeDir, phase) + if err != nil { + return nil, fmt.Errorf("build shared prompt file reference for %q: %w", phase, err) + } entry := map[string]any{ "mode": "subagent", "hidden": true, diff --git a/internal/components/sdd/profiles_lifecycle_test.go b/internal/components/sdd/profiles_lifecycle_test.go index 8d1d1a183..659ecabdb 100644 --- a/internal/components/sdd/profiles_lifecycle_test.go +++ b/internal/components/sdd/profiles_lifecycle_test.go @@ -58,7 +58,7 @@ func TestProfileLifecycle_FullCRUD(t *testing.T) { OrchestratorModel: haikuModel, } - overlayBytes, err := GenerateProfileOverlay(cheapProfile, home) + overlayBytes, err := GenerateProfileOverlay(cheapProfile, home, settingsPath) if err != nil { t.Fatalf("GenerateProfileOverlay(): %v", err) } @@ -121,7 +121,7 @@ func TestProfileLifecycle_FullCRUD(t *testing.T) { Name: "cheap", OrchestratorModel: sonnetModel, } - editOverlayBytes, err := GenerateProfileOverlay(editedProfile, home) + editOverlayBytes, err := GenerateProfileOverlay(editedProfile, home, settingsPath) if err != nil { t.Fatalf("GenerateProfileOverlay() for edit: %v", err) } @@ -224,7 +224,7 @@ func TestProfileLifecycle_TwoProfiles(t *testing.T) { cheapOverlay, err := GenerateProfileOverlay(model.Profile{ Name: "cheap", OrchestratorModel: model.ModelAssignment{ProviderID: "anthropic", ModelID: "claude-haiku-3-5"}, - }, home) + }, home, settingsPath) if err != nil { t.Fatalf("GenerateProfileOverlay(cheap): %v", err) } @@ -233,7 +233,7 @@ func TestProfileLifecycle_TwoProfiles(t *testing.T) { premiumOverlay, err := GenerateProfileOverlay(model.Profile{ Name: "premium", OrchestratorModel: model.ModelAssignment{ProviderID: "anthropic", ModelID: "claude-opus-4-5"}, - }, home) + }, home, settingsPath) if err != nil { t.Fatalf("GenerateProfileOverlay(premium): %v", err) } diff --git a/internal/components/sdd/profiles_test.go b/internal/components/sdd/profiles_test.go index 90c62650f..8fec746f1 100644 --- a/internal/components/sdd/profiles_test.go +++ b/internal/components/sdd/profiles_test.go @@ -403,10 +403,14 @@ func makeHaikuProfile() model.Profile { } } +func openCodeSettingsPathForTest(home string) string { + return filepath.Join(home, ".config", "opencode", "opencode.json") +} + func TestGenerateProfileOverlay_Structure(t *testing.T) { home := t.TempDir() - overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home) + overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } @@ -479,7 +483,7 @@ func TestGenerateProfileOverlay_Structure(t *testing.T) { func TestGenerateProfileOverlay_PermissionScoped(t *testing.T) { home := t.TempDir() - overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home) + overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } @@ -524,7 +528,7 @@ func TestGenerateProfileOverlay_JDAssignmentsGenerateSuffixedAgents(t *testing.T profile.PhaseAssignments["jd-judge-b"] = model.ModelAssignment{ProviderID: "openai", ModelID: "gpt-5.1"} profile.PhaseAssignments["jd-fix-agent"] = model.ModelAssignment{ProviderID: "anthropic", ModelID: "claude-sonnet-4-20250514"} - overlay, err := GenerateProfileOverlay(profile, home) + overlay, err := GenerateProfileOverlay(profile, home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } @@ -597,7 +601,7 @@ func TestGenerateProfileOverlay_JDAssignmentsGenerateSuffixedAgents(t *testing.T func TestGenerateProfileOverlay_NoJDAssignmentsUsesGlobalJDAgents(t *testing.T) { home := t.TempDir() - overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home) + overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } @@ -632,7 +636,7 @@ func TestGenerateProfileOverlay_NoJDAssignmentsUsesGlobalJDAgents(t *testing.T) func TestGenerateProfileOverlay_ToolsUseReplaceSentinel(t *testing.T) { home := t.TempDir() - overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home) + overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } @@ -733,7 +737,7 @@ func TestDefaultOverlayToolsUseReplaceSentinel(t *testing.T) { func TestGenerateProfileOverlay_TaskPermissionsBlockCrossProfileDelegation(t *testing.T) { home := t.TempDir() - overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home) + overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } @@ -771,13 +775,11 @@ func TestGenerateProfileOverlay_TaskPermissionsBlockCrossProfileDelegation(t *te func TestGenerateProfileOverlay_SubAgentFileRefs(t *testing.T) { home := t.TempDir() - overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home) + overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } - promptDir := SharedPromptDir(home) - var root map[string]any if err := json.Unmarshal(overlay, &root); err != nil { t.Fatalf("overlay is not valid JSON: %v", err) @@ -788,7 +790,10 @@ func TestGenerateProfileOverlay_SubAgentFileRefs(t *testing.T) { key := phase + "-cheap" agent := agentMap[key].(map[string]any) prompt, _ := agent["prompt"].(string) - expectedRef := "{file:" + filepath.ToSlash(filepath.Join(promptDir, phase+".md")) + "}" + expectedRef, err := SharedPromptFileRef(openCodeSettingsPathForTest(home), home, phase) + if err != nil { + t.Fatalf("SharedPromptFileRef() error = %v", err) + } if prompt != expectedRef { t.Errorf("sub-agent %q prompt = %q, want %q", key, prompt, expectedRef) } @@ -798,7 +803,7 @@ func TestGenerateProfileOverlay_SubAgentFileRefs(t *testing.T) { func TestGenerateProfileOverlay_OrchestratorPromptSuffixed(t *testing.T) { home := t.TempDir() - overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home) + overlay, err := GenerateProfileOverlay(makeHaikuProfile(), home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } @@ -1106,7 +1111,7 @@ func TestGenerateProfileOverlay_VariantInjected(t *testing.T) { }, } - overlay, err := GenerateProfileOverlay(profile, home) + overlay, err := GenerateProfileOverlay(profile, home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } @@ -1138,7 +1143,7 @@ func TestGenerateProfileOverlay_EmptyEffortClearsVariant(t *testing.T) { }, } - overlay, err := GenerateProfileOverlay(profile, home) + overlay, err := GenerateProfileOverlay(profile, home, openCodeSettingsPathForTest(home)) if err != nil { t.Fatalf("GenerateProfileOverlay() error = %v", err) } diff --git a/internal/components/sdd/prompts.go b/internal/components/sdd/prompts.go index ec248f63e..3b8d647da 100644 --- a/internal/components/sdd/prompts.go +++ b/internal/components/sdd/prompts.go @@ -25,6 +25,21 @@ func SharedPromptDir(homeDir string) string { return filepath.Join(homeDir, ".config", "opencode", "prompts", "sdd") } +// SharedPromptFileRef returns a prompt file reference relative to the settings +// file that will contain it. +func SharedPromptFileRef(settingsPath, homeDir, phase string) (string, error) { + promptPath := filepath.Join(SharedPromptDir(homeDir), phase+".md") + relativePath, err := filepath.Rel(filepath.Dir(settingsPath), promptPath) + if err != nil { + return "", err + } + relativePath = filepath.ToSlash(relativePath) + if !strings.HasPrefix(relativePath, ".") { + relativePath = "./" + relativePath + } + return "{file:" + relativePath + "}", nil +} + // subAgentPhaseOrder is an alias for profilePhaseOrder (defined in profiles.go), // kept for backward compatibility with any code in this file that references it. // Both variables are in the same package and represent the same canonical list. diff --git a/internal/components/sdd/prompts_test.go b/internal/components/sdd/prompts_test.go index 8291963c8..b893068fe 100644 --- a/internal/components/sdd/prompts_test.go +++ b/internal/components/sdd/prompts_test.go @@ -22,6 +22,41 @@ func TestSharedPromptDir(t *testing.T) { } } +func TestSharedPromptFileRef(t *testing.T) { + home := t.TempDir() + tests := []struct { + name string + settingsPath string + want string + }{ + { + name: "OpenCode settings", + settingsPath: filepath.Join(home, ".config", "opencode", "opencode.json"), + want: "{file:./prompts/sdd/sdd-apply.md}", + }, + { + name: "Kilocode settings", + settingsPath: filepath.Join(home, ".config", "kilo", "opencode.json"), + want: "{file:../opencode/prompts/sdd/sdd-apply.md}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := SharedPromptFileRef(tt.settingsPath, home, "sdd-apply") + if err != nil { + t.Fatalf("SharedPromptFileRef() error = %v", err) + } + if got != tt.want { + t.Fatalf("SharedPromptFileRef() = %q, want %q", got, tt.want) + } + if strings.Contains(got, filepath.ToSlash(home)) { + t.Fatalf("SharedPromptFileRef() = %q, contains home path", got) + } + }) + } +} + func readOpenCodeAgents(t *testing.T, settingsPath string) map[string]any { t.Helper() content, err := os.ReadFile(settingsPath) @@ -391,16 +426,19 @@ func TestInjectOpenCodeMultiModeSubagentPromptsUseFilePaths(t *testing.T) { t.Fatalf("ReadFile(opencode.json) error = %v", err) } - promptDir := SharedPromptDir(home) - - text := strings.ReplaceAll(string(content), `\\`, `/`) + text := string(content) for _, phase := range []string{"sdd-init", "sdd-explore", "sdd-propose", "sdd-spec", "sdd-design", "sdd-tasks", "sdd-apply", "sdd-verify", "sdd-archive", "sdd-onboard"} { - expectedRef := "{file:" + filepath.Join(promptDir, phase+".md") + "}" - expectedRef = strings.ReplaceAll(expectedRef, `\`, `/`) + expectedRef, err := SharedPromptFileRef(settingsPath, home, phase) + if err != nil { + t.Fatalf("SharedPromptFileRef() error = %v", err) + } if !strings.Contains(text, expectedRef) { t.Errorf("opencode.json sub-agent %q missing {file:...} reference %q", phase, expectedRef) } } + if strings.Contains(text, filepath.ToSlash(home)) { + t.Fatalf("opencode.json contains home-specific absolute path %q", filepath.ToSlash(home)) + } } func TestWriteSharedPromptFilesOmitCodeGraphGuidanceByDefault(t *testing.T) { diff --git a/testdata/golden/sdd-opencode-multi-settings.golden b/testdata/golden/sdd-opencode-multi-settings.golden index 108bf15d3..24d14a301 100644 --- a/testdata/golden/sdd-opencode-multi-settings.golden +++ b/testdata/golden/sdd-opencode-multi-settings.golden @@ -146,7 +146,7 @@ "description": "Implement code changes from task definitions", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-apply.md}", + "prompt": "{file:./prompts/sdd/sdd-apply.md}", "tools": { "bash": true, "edit": true, @@ -158,7 +158,7 @@ "description": "Archive completed change artifacts", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-archive.md}", + "prompt": "{file:./prompts/sdd/sdd-archive.md}", "tools": { "bash": true, "edit": true, @@ -170,7 +170,7 @@ "description": "Create technical design from proposals", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-design.md}", + "prompt": "{file:./prompts/sdd/sdd-design.md}", "tools": { "bash": true, "edit": true, @@ -182,7 +182,7 @@ "description": "Investigate codebase and think through ideas", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-explore.md}", + "prompt": "{file:./prompts/sdd/sdd-explore.md}", "tools": { "bash": true, "edit": true, @@ -194,7 +194,7 @@ "description": "Bootstrap SDD context and project configuration", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-init.md}", + "prompt": "{file:./prompts/sdd/sdd-init.md}", "tools": { "bash": true, "edit": true, @@ -206,7 +206,7 @@ "description": "Guide user through a complete SDD cycle using their real codebase", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-onboard.md}", + "prompt": "{file:./prompts/sdd/sdd-onboard.md}", "tools": { "bash": true, "edit": true, @@ -218,7 +218,7 @@ "description": "Create change proposals from explorations", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-propose.md}", + "prompt": "{file:./prompts/sdd/sdd-propose.md}", "tools": { "bash": true, "edit": true, @@ -230,7 +230,7 @@ "description": "Write detailed specifications from proposals", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-spec.md}", + "prompt": "{file:./prompts/sdd/sdd-spec.md}", "tools": { "bash": true, "edit": true, @@ -242,7 +242,7 @@ "description": "Break down specs and designs into implementation tasks", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-tasks.md}", + "prompt": "{file:./prompts/sdd/sdd-tasks.md}", "tools": { "bash": true, "edit": true, @@ -254,7 +254,7 @@ "description": "Validate implementation against specs", "hidden": true, "mode": "subagent", - "prompt": "{file:{{HOME}}/.config/opencode/prompts/sdd/sdd-verify.md}", + "prompt": "{file:./prompts/sdd/sdd-verify.md}", "tools": { "bash": true, "edit": true,