@tudeorangbiasa/sdd-multiagent-opencode 0.3.0 → 0.4.0

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.
@@ -177,10 +177,10 @@ Ask:
177
177
 
178
178
  > Explainer: SDD routes different agents to different models. Paid models get planning and orchestration roles. Free models get implementation and exploration roles. This section configures that routing.
179
179
 
180
- Present the default model profile (from `.sdd/templates/model-profile-template.json`) and ask:
180
+ Read `~/.config/opencode/sdd-multiagent-opencode.json` and summarize the current global routing. If the file is missing or still uses defaults, ask:
181
181
  1. **Do you have a paid model subscription?** (OpenAI Plus, OpenCode Go, OpenCode Zen, or direct API keys)
182
182
  2. **If yes, which provider?** This determines which agents get routed to paid models.
183
- 3. **If no, accept the free-model defaults.** Implementation will still work but planning may be slower.
183
+ 3. **If no, keep the free-model defaults.** Implementation will still work but planning may be slower.
184
184
 
185
185
  ### Section D — Issue Tracker (ALL types)
186
186
 
@@ -293,18 +293,13 @@ If Phase 1 used the sniff test (no subagent spawned), write the stack profile no
293
293
 
294
294
  This ensures `.sdd/stack-profile.json` always exists after `/sdd-construct`, regardless of whether a subagent was used.
295
295
 
296
- ### 3.2 `.sdd/model-profile.json`
296
+ ### 3.2 Global SDD config
297
297
 
298
- Populate the template with model routing based on Phase 2 Section C:
299
- - If user has paid subscription → route orchestrator and planner to paid model
300
- - If user has no paid subscription → use free model defaults
301
- - Keep explorer, implementer, verifier, reviewer on free models
298
+ Do not create per-project model or reasoning profiles. SDD runtime configuration lives in `~/.config/opencode/sdd-multiagent-opencode.json`.
302
299
 
303
- ### 3.3 `.sdd/reasoning-profile.json`
300
+ If the global config is missing, report it and suggest creating it. Do not write outside the project unless the user explicitly asks.
304
301
 
305
- Copy the template. No changes needed — defaults are appropriate for all project sizes.
306
-
307
- ### 3.4 `CONTEXT.md` (at repo root)
302
+ ### 3.3 `CONTEXT.md` (at repo root)
308
303
 
309
304
  Create the domain glossary. Format:
310
305
 
@@ -627,8 +622,7 @@ Before reporting completion, verify:
627
622
  ### Universal checks (ALL project types):
628
623
  - [ ] `.sdd/stack-profile.json` exists (written by explorer or by planner after sniff test)
629
624
  - [ ] `.sdd/project-profile.json` exists and has non-null values for detected stack
630
- - [ ] `.sdd/model-profile.json` exists and has agent routing configured
631
- - [ ] `.sdd/reasoning-profile.json` exists
625
+ - [ ] `~/.config/opencode/sdd-multiagent-opencode.json` exists or missing global config was reported
632
626
  - [ ] `CONTEXT.md` exists at repo root (even if empty tables)
633
627
  - [ ] `docs/adr/0001-project-initialization.md` exists
634
628
  - [ ] `AGENTS.md` or `CLAUDE.md` has `## Agent skills` block
@@ -657,7 +651,7 @@ Before final output, verify:
657
651
  - [ ] Grilling questions were asked one at a time (not dumped all at once)
658
652
  - [ ] CONTEXT.md contains domain terms from the grilling session, not generic placeholders
659
653
  - [ ] ADR documents actual decisions made during the session
660
- - [ ] Model routing reflects user's subscription status, not defaults
654
+ - [ ] Global model routing reflects user's subscription status, or missing global config was reported
661
655
  - [ ] No file paths or code snippets in CONTEXT.md
662
656
  - [ ] Type-specific guardrails match classified project type
663
657
  - [ ] No DESIGN.md generated for non-UI projects (backend, library, cli)
@@ -675,8 +669,6 @@ changed: SDD project initialized
675
669
  Artifacts created:
676
670
  - `.sdd/stack-profile.json` — raw stack detection results (from explorer or sniff test)
677
671
  - `.sdd/project-profile.json` — stack detection results
678
- - `.sdd/model-profile.json` — model routing configuration
679
- - `.sdd/reasoning-profile.json` — reasoning effort per agent
680
672
  - `CONTEXT.md` — domain glossary
681
673
  - `docs/adr/0001-project-initialization.md` — initialization decisions
682
674
  - `docs/agents/issue-tracker.md` — issue tracker configuration
@@ -1,11 +1,23 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
3
 
5
- const pluginDir = path.dirname(fileURLToPath(import.meta.url));
6
4
  const LEVELS = new Set(["low", "medium", "high"]);
7
5
  const pendingOverrides = new Map();
8
6
  const recentCommands = new Map();
7
+ const globalConfigPath = path.join(
8
+ process.env.HOME || process.env.USERPROFILE || "",
9
+ ".config",
10
+ "opencode",
11
+ "sdd-multiagent-opencode.json"
12
+ );
13
+
14
+ const DEFAULT_REASONING = {
15
+ default: "low",
16
+ applyProviderOptions: true,
17
+ resetToolOverrideAfterNextRequest: true,
18
+ agents: {},
19
+ commands: {},
20
+ };
9
21
 
10
22
  function readJson(filePath) {
11
23
  if (!fs.existsSync(filePath)) return null;
@@ -18,24 +30,8 @@ function readJson(filePath) {
18
30
  }
19
31
 
20
32
  function readProfile() {
21
- const candidates = [
22
- path.join(process.cwd(), ".sdd", "reasoning-profile.json"),
23
- path.join(pluginDir, "..", "..", ".sdd", "reasoning-profile.json"),
24
- path.join(pluginDir, "..", ".sdd", "reasoning-profile.json"),
25
- ];
26
-
27
- for (const candidate of candidates) {
28
- const profile = readJson(candidate);
29
- if (profile) return profile;
30
- }
31
-
32
- return {
33
- default: "low",
34
- applyProviderOptions: true,
35
- resetToolOverrideAfterNextRequest: true,
36
- agents: {},
37
- commands: {},
38
- };
33
+ const pluginConfig = readJson(globalConfigPath);
34
+ return pluginConfig?.reasoning ?? DEFAULT_REASONING;
39
35
  }
40
36
 
41
37
  function normalizeLevel(level, fallback = "low") {
@@ -1,57 +1,99 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const pluginDir = path.dirname(fileURLToPath(import.meta.url));
6
-
7
- function readProfile() {
8
- const candidates = [
9
- path.join(process.cwd(), ".sdd", "model-profile.json"),
10
- path.join(pluginDir, "..", "..", ".sdd", "model-profile.json"),
11
- path.join(pluginDir, "..", ".sdd", "model-profile.json"),
12
- ];
13
-
14
- for (const profilePath of candidates) {
15
- if (!fs.existsSync(profilePath)) {
16
- continue;
17
- }
18
-
19
- try {
20
- return JSON.parse(fs.readFileSync(profilePath, "utf-8"));
21
- } catch {
22
- return null;
23
- }
3
+
4
+ const globalConfigPath = path.join(
5
+ process.env.HOME || process.env.USERPROFILE || "",
6
+ ".config",
7
+ "opencode",
8
+ "sdd-multiagent-opencode.json"
9
+ );
10
+ const DEFAULT_PLUGIN_CONFIG = {
11
+ model: "openai/gpt-5.5",
12
+ small_model: "opencode/deepseek-v4-flash-free",
13
+ agent: {
14
+ "sdd-orchestrator": { model: "openai/gpt-5.5" },
15
+ "sdd-planner": { model: "openai/gpt-5.5" },
16
+ "sdd-explorer": { model: "opencode/deepseek-v4-flash-free" },
17
+ "sdd-implementer": { model: "opencode/deepseek-v4-flash-free" },
18
+ "sdd-verifier": { model: "opencode/qwen3.6-plus-free" },
19
+ "sdd-reviewer": { model: "opencode/minimax-m2.5-free" },
20
+ "sdd-quick": { model: "opencode/qwen3.6-plus-free" },
21
+ },
22
+ reasoning: {
23
+ default: "low",
24
+ applyProviderOptions: true,
25
+ resetToolOverrideAfterNextRequest: true,
26
+ agents: {
27
+ general: "medium",
28
+ plan: "high",
29
+ "sdd-orchestrator": "high",
30
+ "sdd-planner": "high",
31
+ "sdd-explorer": "low",
32
+ "sdd-implementer": "medium",
33
+ "sdd-verifier": "medium",
34
+ "sdd-reviewer": "medium",
35
+ "sdd-quick": "low",
36
+ },
37
+ commands: {
38
+ "sdd-explore": "medium",
39
+ "sdd-propose": "high",
40
+ "sdd-apply": "medium",
41
+ "sdd-ship": "medium",
42
+ "sdd-quick": "low",
43
+ },
44
+ },
45
+ };
46
+
47
+ function ensureConfigFile() {
48
+ if (fs.existsSync(globalConfigPath)) return;
49
+
50
+ try {
51
+ fs.mkdirSync(path.dirname(globalConfigPath), { recursive: true });
52
+ fs.writeFileSync(globalConfigPath, `${JSON.stringify(DEFAULT_PLUGIN_CONFIG, null, 2)}\n`);
53
+ } catch {
54
+ // If the global config cannot be seeded, continue with OpenCode's existing config.
24
55
  }
56
+ }
57
+
58
+ function readConfig() {
59
+ ensureConfigFile();
60
+ if (!fs.existsSync(globalConfigPath)) return null;
25
61
 
26
- return null;
62
+ try {
63
+ return JSON.parse(fs.readFileSync(globalConfigPath, "utf-8"));
64
+ } catch {
65
+ return null;
66
+ }
27
67
  }
28
68
 
29
69
  export default async () => {
30
70
  return {
31
71
  config: (cfg) => {
32
- const profile = readProfile();
33
- if (!profile) {
72
+ const pluginConfig = readConfig();
73
+ if (!pluginConfig) {
34
74
  return;
35
75
  }
36
76
 
37
- if (profile.defaultPrimary) {
38
- cfg.model = profile.defaultPrimary;
77
+ if (pluginConfig.model) {
78
+ cfg.model = pluginConfig.model;
39
79
  }
40
80
 
41
- if (profile.small) {
42
- cfg.small_model = profile.small;
81
+ if (pluginConfig.small_model) {
82
+ cfg.small_model = pluginConfig.small_model;
43
83
  }
44
84
 
45
85
  if (!cfg.agent) {
46
86
  cfg.agent = {};
47
87
  }
48
88
 
49
- for (const [agentName, model] of Object.entries(profile.agents ?? {})) {
89
+ for (const [agentName, agentConfig] of Object.entries(pluginConfig.agent ?? {})) {
90
+ if (!agentConfig?.model) continue;
91
+
50
92
  if (!cfg.agent[agentName]) {
51
93
  cfg.agent[agentName] = {};
52
94
  }
53
95
 
54
- cfg.agent[agentName].model = model;
96
+ cfg.agent[agentName].model = agentConfig.model;
55
97
  }
56
98
  },
57
99
  };
@@ -1,6 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import sddAutoReasoning from "./sdd-auto-reasoning.js";
5
+ import sddModelRouter from "./sdd-model-router.js";
4
6
 
5
7
  const __filename = fileURLToPath(import.meta.url);
6
8
  const __dirname = path.dirname(__filename);
@@ -390,7 +392,7 @@ const COMMANDS = {
390
392
  "sdd-construct": {
391
393
  description: "Initialize SDD workflow for a new or existing project — explore stack, grill vision, scaffold config, create CONTEXT.md and ADRs",
392
394
  agent: "sdd-planner",
393
- prompt: `Initialize the "spine" of an SDD-driven project. This is a one-time setup command that creates the foundational artifacts every AI-assisted project needs.
395
+ template: `Initialize the "spine" of an SDD-driven project. This is a one-time setup command that creates the foundational artifacts every AI-assisted project needs.
394
396
 
395
397
  ## Usage
396
398
 
@@ -478,7 +480,7 @@ After presenting findings, walk the user through decisions one at a time. Do not
478
480
  3. Team size: solo, small team (2-5), or large team (5+)?
479
481
 
480
482
  ### Section C — Model Budget (ALL types)
481
- Present the default model profile and ask about paid model subscriptions.
483
+ Explain that SDD model and reasoning routing are configured globally in ~/.config/opencode/sdd-multiagent-opencode.json. Ask about paid model subscriptions only if the global config is missing or obviously still using defaults.
482
484
 
483
485
  ### Section D — Issue Tracker (ALL types)
484
486
  Options: GitHub, GitLab, Local markdown, Other.
@@ -495,7 +497,7 @@ Options: GitHub, GitLab, Local markdown, Other.
495
497
 
496
498
  ## Phase 3: Scaffold Artifacts
497
499
 
498
- Generate: .sdd/project-profile.json, .sdd/stack-profile.json, .sdd/model-profile.json, .sdd/reasoning-profile.json, CONTEXT.md, docs/adr/0001-project-initialization.md, type-specific artifacts, docs/agents/issue-tracker.md, and update AGENTS.md with agent skills block.
500
+ Generate: .sdd/project-profile.json, .sdd/stack-profile.json, CONTEXT.md, docs/adr/0001-project-initialization.md, type-specific artifacts, docs/agents/issue-tracker.md, and update AGENTS.md with agent skills block. Do not create per-project model or reasoning profiles.
499
501
 
500
502
  ## Phase 4: Verification
501
503
 
@@ -511,7 +513,7 @@ End with:
511
513
  changed: SDD project initialized
512
514
 
513
515
  Artifacts created:
514
- - .sdd/stack-profile.json, .sdd/project-profile.json, .sdd/model-profile.json, .sdd/reasoning-profile.json
516
+ - .sdd/stack-profile.json, .sdd/project-profile.json
515
517
  - CONTEXT.md, docs/adr/0001-project-initialization.md, docs/agents/issue-tracker.md
516
518
  - AGENTS.md (agent skills block)
517
519
  - <type-specific artifacts>
@@ -523,7 +525,7 @@ Next: run /sdd-propose "<your first feature>" to start building.`,
523
525
  "sdd-explore": {
524
526
  description: "Investigate an idea, bug, or code area without changing source code",
525
527
  agent: "sdd-explorer",
526
- prompt: `Explore the request safely before committing to a change.
528
+ template: `Explore the request safely before committing to a change.
527
529
 
528
530
  ## Usage
529
531
 
@@ -562,7 +564,7 @@ Return a concise investigation report:
562
564
  "sdd-propose": {
563
565
  description: "Create one focused SDD change with proposal, spec, design, and tasks",
564
566
  agent: "sdd-planner",
565
- prompt: `Create a compact, reviewable change plan. Stop before implementation.
567
+ template: `Create a compact, reviewable change plan. Stop before implementation.
566
568
 
567
569
  ## Usage
568
570
 
@@ -617,7 +619,7 @@ Next: review the artifacts, then run /sdd-apply <change-id>.`,
617
619
  "sdd-apply": {
618
620
  description: "Implement an approved SDD change from tasks.md",
619
621
  agent: "sdd-implementer",
620
- prompt: `Implement an existing change from specs/active/<change-id>/.
622
+ template: `Implement an existing change from specs/active/<change-id>/.
621
623
 
622
624
  ## Usage
623
625
 
@@ -674,7 +676,7 @@ Next: run /sdd-ship <change-id> for final review.`,
674
676
  "sdd-ship": {
675
677
  description: "Final verification and readiness review for an SDD change",
676
678
  agent: "sdd-reviewer",
677
- prompt: `Verify a completed change before considering it ready.
679
+ template: `Verify a completed change before considering it ready.
678
680
 
679
681
  ## Usage
680
682
 
@@ -723,7 +725,7 @@ Findings first (critical, major, minor), Verification results, Decision (ready:
723
725
  "sdd-quick": {
724
726
  description: "Quick one-shot fix for cosmetic/config changes with CLI wrapper for deterministic checks",
725
727
  agent: "sdd-implementer",
726
- prompt: `Execute a small, safe change with minimal overhead. Uses CLI wrapper for classification, pre-flight, verification, and ledger. LLM is only used for the actual edit.
728
+ template: `Execute a small, safe change with minimal overhead. Uses CLI wrapper for classification, pre-flight, verification, and ledger. LLM is only used for the actual edit.
727
729
 
728
730
  ## Usage
729
731
 
@@ -802,8 +804,12 @@ suggestion: Run /sdd-propose "<request>" for full workflow.`,
802
804
  // ─── Plugin Export ───────────────────────────────────────────────────────────
803
805
 
804
806
  export default async ({ client, project, directory, $ }) => {
807
+ const modelRouterHooks = await sddModelRouter({ client, project, directory, $ });
808
+ const autoReasoningHooks = await sddAutoReasoning({ client, project, directory, $ });
809
+
805
810
  return {
806
811
  agent: AGENTS,
812
+ ...autoReasoningHooks,
807
813
 
808
814
  config(cfg) {
809
815
  // Register SDD skills path
@@ -835,54 +841,7 @@ export default async ({ client, project, directory, $ }) => {
835
841
  cfg.permission.skill["sdd-*"] = "allow";
836
842
  }
837
843
 
838
- // Chain other SDD plugins via config hook
839
- // Load sdd-model-router
840
- try {
841
- const modelRouterPath = path.join(__dirname, "sdd-model-router.js");
842
- if (fs.existsSync(modelRouterPath)) {
843
- import(modelRouterPath).then(async (mod) => {
844
- const pluginFn = mod.default;
845
- if (pluginFn) {
846
- const result = await pluginFn({ client, project, directory, $ });
847
- if (result.config) {
848
- result.config(cfg);
849
- }
850
- }
851
- });
852
- }
853
- } catch {
854
- // sdd-model-router not available, skip
855
- }
856
-
857
- // Load sdd-auto-reasoning
858
- try {
859
- const reasoningPath = path.join(__dirname, "sdd-auto-reasoning.js");
860
- if (fs.existsSync(reasoningPath)) {
861
- import(reasoningPath).then(async (mod) => {
862
- const pluginFn = mod.default;
863
- if (pluginFn) {
864
- const result = await pluginFn({ client, project, directory, $ });
865
- // Merge all hooks from auto-reasoning
866
- for (const [hookName, hookFn] of Object.entries(result)) {
867
- if (hookName !== "config" && typeof hookFn === "function") {
868
- // Chain hook — call existing then new
869
- const existing = cfg[hookName];
870
- if (typeof existing === "function") {
871
- cfg[hookName] = async (...args) => {
872
- await existing(...args);
873
- await hookFn(...args);
874
- };
875
- } else {
876
- cfg[hookName] = hookFn;
877
- }
878
- }
879
- }
880
- }
881
- });
882
- }
883
- } catch {
884
- // sdd-auto-reasoning not available, skip
885
- }
844
+ modelRouterHooks.config?.(cfg);
886
845
  },
887
846
  };
888
847
  };
package/GUIDE.md CHANGED
@@ -48,7 +48,7 @@ Run this once at the start of a project to set up the "spine" — stack detectio
48
48
  - Section F: Type-specific guardrails (conditional)
49
49
 
50
50
  3. **Phase 3: Scaffold Artifacts**
51
- - `.sdd/project-profile.json`, `.sdd/model-profile.json`, `.sdd/reasoning-profile.json`
51
+ - `.sdd/project-profile.json`, plus global runtime config in `~/.config/opencode/sdd-multiagent-opencode.json`
52
52
  - `CONTEXT.md` (domain glossary)
53
53
  - `docs/adr/0001-project-initialization.md`
54
54
  - Type-specific artifacts (conditional):
package/README.md CHANGED
@@ -41,7 +41,7 @@ external UI skills # impeccable, taste, nothing-design, etc.
41
41
  | **sdd-reviewer** | configurable | Code review (security, performance, spec compliance) |
42
42
  | **sdd-quick** | configurable | Small cosmetic/config edits (~200 tokens via CLI wrapper) |
43
43
 
44
- **All models are configurable** via `.sdd/model-profile.json`. Edit this file to change which model each agent uses. See [Model Settings](#model-settings) below.
44
+ **All models are configurable** via `~/.config/opencode/sdd-multiagent-opencode.json`. Edit this file to change which model each agent uses. See [Model Settings](#model-settings) below.
45
45
 
46
46
  ## Quick Start
47
47
 
@@ -64,7 +64,7 @@ To pin a specific version:
64
64
  ```json
65
65
  {
66
66
  "plugin": [
67
- "@tudeorangbiasa/sdd-multiagent-opencode@0.2.2"
67
+ "@tudeorangbiasa/sdd-multiagent-opencode@0.4.0"
68
68
  ]
69
69
  }
70
70
  ```
@@ -116,7 +116,11 @@ Most work starts with `/sdd-propose`, then `/sdd-apply`, then `/sdd-ship`. Use `
116
116
 
117
117
  ## Model Settings
118
118
 
119
- The installer creates `.sdd/model-profile.json`. Edit this file to change which model each OpenCode agent uses.
119
+ SDD model and reasoning routing are configured in one global plugin config:
120
+
121
+ `~/.config/opencode/sdd-multiagent-opencode.json`
122
+
123
+ Edit this file to change which model each SDD agent uses. This package does not read per-project model or reasoning profile files at runtime.
120
124
 
121
125
  **⚠️ Requirement:** SDD works best with at least **ONE paid/subscription model** for orchestrator and planner. These roles need strong reasoning + multimodal capabilities that free models lack.
122
126
 
@@ -223,22 +227,43 @@ Models available at no cost via OpenCode Zen. Run `opencode models opencode | gr
223
227
  - `groq/llama-3.3-70b-versatile` — Llama 3.3 70B via Groq
224
228
  - `openrouter/deepseek-r1-free` — DeepSeek R1 via OpenRouter (free)
225
229
 
226
- ### Default Profile
230
+ ### Default Config
227
231
 
228
232
  Recommended starting configuration (orchestrator/planner use OpenAI GPT 5.5, others use free):
229
233
 
230
234
  ```json
231
235
  {
232
- "defaultPrimary": "openai/gpt-5.5",
233
- "small": "opencode/deepseek-v4-flash-free",
234
- "agents": {
235
- "sdd-orchestrator": "openai/gpt-5.5",
236
- "sdd-planner": "openai/gpt-5.5",
237
- "sdd-explorer": "opencode/deepseek-v4-flash-free",
238
- "sdd-implementer": "opencode/deepseek-v4-flash-free",
239
- "sdd-verifier": "opencode/qwen3.6-plus-free",
240
- "sdd-reviewer": "opencode/minimax-m2.5-free",
241
- "sdd-quick": "opencode/qwen3.6-plus-free"
236
+ "model": "openai/gpt-5.5",
237
+ "small_model": "opencode/deepseek-v4-flash-free",
238
+ "agent": {
239
+ "sdd-orchestrator": { "model": "openai/gpt-5.5" },
240
+ "sdd-planner": { "model": "openai/gpt-5.5" },
241
+ "sdd-explorer": { "model": "opencode/deepseek-v4-flash-free" },
242
+ "sdd-implementer": { "model": "opencode/deepseek-v4-flash-free" },
243
+ "sdd-verifier": { "model": "opencode/qwen3.6-plus-free" },
244
+ "sdd-reviewer": { "model": "opencode/minimax-m2.5-free" },
245
+ "sdd-quick": { "model": "opencode/qwen3.6-plus-free" }
246
+ },
247
+ "reasoning": {
248
+ "default": "low",
249
+ "applyProviderOptions": true,
250
+ "resetToolOverrideAfterNextRequest": true,
251
+ "agents": {
252
+ "sdd-orchestrator": "high",
253
+ "sdd-planner": "high",
254
+ "sdd-explorer": "low",
255
+ "sdd-implementer": "medium",
256
+ "sdd-verifier": "medium",
257
+ "sdd-reviewer": "medium",
258
+ "sdd-quick": "low"
259
+ },
260
+ "commands": {
261
+ "sdd-explore": "medium",
262
+ "sdd-propose": "high",
263
+ "sdd-apply": "medium",
264
+ "sdd-ship": "medium",
265
+ "sdd-quick": "low"
266
+ }
242
267
  }
243
268
  }
244
269
  ```
@@ -247,16 +272,16 @@ Recommended starting configuration (orchestrator/planner use OpenAI GPT 5.5, oth
247
272
 
248
273
  ```json
249
274
  {
250
- "defaultPrimary": "opencode-go/deepseek-v4-pro",
251
- "small": "opencode/deepseek-v4-flash-free",
252
- "agents": {
253
- "sdd-orchestrator": "opencode-go/deepseek-v4-pro",
254
- "sdd-planner": "opencode-go/deepseek-v4-pro",
255
- "sdd-explorer": "opencode/deepseek-v4-flash-free",
256
- "sdd-implementer": "opencode/deepseek-v4-flash-free",
257
- "sdd-verifier": "opencode-go/kimi-k2.6",
258
- "sdd-reviewer": "opencode/minimax-m2.5-free",
259
- "sdd-quick": "opencode-go/qwen3.6-plus"
275
+ "model": "opencode-go/deepseek-v4-pro",
276
+ "small_model": "opencode/deepseek-v4-flash-free",
277
+ "agent": {
278
+ "sdd-orchestrator": { "model": "opencode-go/deepseek-v4-pro" },
279
+ "sdd-planner": { "model": "opencode-go/deepseek-v4-pro" },
280
+ "sdd-explorer": { "model": "opencode/deepseek-v4-flash-free" },
281
+ "sdd-implementer": { "model": "opencode/deepseek-v4-flash-free" },
282
+ "sdd-verifier": { "model": "opencode-go/kimi-k2.6" },
283
+ "sdd-reviewer": { "model": "opencode/minimax-m2.5-free" },
284
+ "sdd-quick": { "model": "opencode-go/qwen3.6-plus" }
260
285
  }
261
286
  }
262
287
  ```
@@ -270,13 +295,13 @@ Recommended starting configuration (orchestrator/planner use OpenAI GPT 5.5, oth
270
295
  - **Reviewer** → MiniMax M2.5 free: structured code review output
271
296
  - **Quick** → Qwen3.6 Plus free: minimal prompt for small cosmetic/config edits (~200 tokens)
272
297
 
273
- `.opencode/plugins/sdd-model-router.js` reads this file at OpenCode startup and applies the model settings. **Restart OpenCode after editing it.**
298
+ `.opencode/plugins/sdd-model-router.js` reads `~/.config/opencode/sdd-multiagent-opencode.json` at OpenCode startup and applies the model settings. **Restart OpenCode after editing it.**
274
299
 
275
- **Important:** Do NOT set models in `opencode.json` they will be ignored. All model routing goes through `model-profile.json`.
300
+ **Important:** Do NOT set SDD agent models in `opencode.json`. All SDD model routing goes through `~/.config/opencode/sdd-multiagent-opencode.json`.
276
301
 
277
302
  ### Auto Reasoning
278
303
 
279
- The installer also creates `.sdd/reasoning-profile.json` and installs `.opencode/plugins/sdd-auto-reasoning.js`.
304
+ The plugin installs `.opencode/plugins/sdd-auto-reasoning.js` and reads the `reasoning` section from `~/.config/opencode/sdd-multiagent-opencode.json`.
280
305
 
281
306
  The plugin reverse-engineers the behavior of `@howaboua/pi-auto-reasoning-tool` for OpenCode:
282
307
  - sets reasoning effort automatically per agent and command
@@ -298,7 +323,7 @@ Default routing:
298
323
  }
299
324
  ```
300
325
 
301
- Restart OpenCode after editing `.sdd/reasoning-profile.json`.
326
+ Restart OpenCode after editing `~/.config/opencode/sdd-multiagent-opencode.json`.
302
327
 
303
328
  ### Manual Install
304
329
 
@@ -380,8 +405,6 @@ For small cosmetic fixes:
380
405
  .sdd/
381
406
  ├── config.json # Project configuration
382
407
  ├── project-profile.json # Stack and skill routing config
383
- ├── model-profile.json # Model routing per agent
384
- ├── reasoning-profile.json # Reasoning effort per agent/command
385
408
  └── templates/ # Document templates
386
409
  ├── proposal-template.md
387
410
  ├── spec-template.md
@@ -407,6 +430,8 @@ specs/
407
430
  └── QUICKFIX_LOG_ARCHIVE.md # Archived ledger entries
408
431
  ```
409
432
 
433
+ Global runtime config lives at `~/.config/opencode/sdd-multiagent-opencode.json`.
434
+
410
435
  ## Workflows
411
436
 
412
437
  ### New Project Setup
@@ -122,6 +122,64 @@ function parseJsonc(filePath) {
122
122
  return JSON.parse(stripped);
123
123
  }
124
124
 
125
+ function readJsonSafe(filePath) {
126
+ if (!fs.existsSync(filePath)) return null;
127
+
128
+ try {
129
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+
135
+ function getNativeAuthPath() {
136
+ const home = process.env.HOME || process.env.USERPROFILE || "";
137
+ const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
138
+ return path.join(xdgDataHome, "opencode", "auth.json");
139
+ }
140
+
141
+ function getProviderId(model) {
142
+ if (typeof model !== "string") return null;
143
+ const separator = model.indexOf("/");
144
+ if (separator <= 0) return null;
145
+ return model.slice(0, separator);
146
+ }
147
+
148
+ function getProviderEnvNames(providerId) {
149
+ const explicit = {
150
+ openai: ["OPENAI_API_KEY"],
151
+ anthropic: ["ANTHROPIC_API_KEY"],
152
+ google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
153
+ opencode: ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"],
154
+ openrouter: ["OPENROUTER_API_KEY"],
155
+ groq: ["GROQ_API_KEY"],
156
+ deepseek: ["DEEPSEEK_API_KEY"],
157
+ minimax: ["MINIMAX_API_KEY"],
158
+ moonshot: ["MOONSHOT_API_KEY"],
159
+ zai: ["ZAI_API_KEY"],
160
+ };
161
+
162
+ if (explicit[providerId]) return explicit[providerId];
163
+
164
+ return [`${providerId.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`];
165
+ }
166
+
167
+ function describeProviderConfig(providerId, opencodeConfig, nativeAuth) {
168
+ const auth = nativeAuth?.[providerId];
169
+ if (auth?.type === "oauth") return "configured via OpenCode native oauth";
170
+ if (auth?.type === "api") return "configured via OpenCode native api auth";
171
+ if (auth) return "configured via OpenCode native auth";
172
+
173
+ const providerConfig = opencodeConfig?.provider?.[providerId];
174
+ if (providerConfig?.options?.apiKey) return "configured via opencode.json provider apiKey";
175
+ if (providerConfig) return "configured via opencode.json provider";
176
+
177
+ const envName = getProviderEnvNames(providerId).find((name) => !!process.env[name]);
178
+ if (envName) return `configured via ${envName}`;
179
+
180
+ return null;
181
+ }
182
+
125
183
  // ─── Backup ──────────────────────────────────────────────────────────────────
126
184
 
127
185
  function createBackup(filePath) {
@@ -177,7 +235,7 @@ function parseArgs(argv) {
177
235
  // ─── Usage ───────────────────────────────────────────────────────────────────
178
236
 
179
237
  function usage() {
180
- console.log(`SDD Multi-Agent OpenCode Installer v0.3.0
238
+ console.log(`SDD Multi-Agent OpenCode Installer v0.4.0
181
239
 
182
240
  Usage:
183
241
  sdd-opencode init [flags] Install SDD workflow into current project
@@ -331,8 +389,6 @@ function installSdd(ctx) {
331
389
 
332
390
  const profileTemplates = [
333
391
  { dest: "project-profile.json", template: "project-profile-template.json", populate: true },
334
- { dest: "model-profile.json", template: "model-profile-template.json", populate: false },
335
- { dest: "reasoning-profile.json", template: "reasoning-profile-template.json", populate: false },
336
392
  ];
337
393
 
338
394
  for (const { dest, template, populate } of profileTemplates) {
@@ -392,16 +448,6 @@ function patchOpencodeJson(ctx) {
392
448
 
393
449
  if (!targetConfig.agent) targetConfig.agent = {};
394
450
 
395
- // Patch compaction from vendor rules
396
- const vendorRulesJson = path.join(packageRoot, "vendor/opencode-agent-rules/opencode.json");
397
- if (fs.existsSync(vendorRulesJson)) {
398
- const rulesConfig = JSON.parse(fs.readFileSync(vendorRulesJson, "utf-8"));
399
- if (rulesConfig.agent?.compaction && !targetConfig.agent.compaction) {
400
- targetConfig.agent.compaction = rulesConfig.agent.compaction;
401
- ctx.installed.push("opencode.json (compaction patch)");
402
- }
403
- }
404
-
405
451
  // Patch plugins
406
452
  const pkgJsonPath = path.join(packageRoot, "opencode.json");
407
453
  if (fs.existsSync(pkgJsonPath)) {
@@ -467,7 +513,7 @@ async function runInit(args) {
467
513
  dryRunFiles: [],
468
514
  };
469
515
 
470
- console.log("\nSDD Multi-Agent OpenCode Installer v0.3.0\n");
516
+ console.log("\nSDD Multi-Agent OpenCode Installer v0.4.0\n");
471
517
  console.log(`Target: ${targetRoot}`);
472
518
  if (ctx.dryRun) console.log("Mode: DRY RUN (no files will be written)\n");
473
519
 
@@ -503,8 +549,8 @@ async function runInit(args) {
503
549
 
504
550
  console.log("\nNext steps:");
505
551
  console.log(" 1. Edit .sdd/project-profile.json with your stack");
506
- console.log(" 2. Run: /sdd-propose \"your first feature\"");
507
- console.log(" 3. Optional TDD: /sdd-propose my-feature --tdd");
552
+ console.log(" 2. Configure models in ~/.config/opencode/sdd-multiagent-opencode.json");
553
+ console.log(" 3. Run: /sdd-propose \"your first feature\"");
508
554
  }
509
555
  }
510
556
 
@@ -605,42 +651,44 @@ async function runDoctor(args) {
605
651
  const cmdCount = fs.existsSync(commandsDir) ? fs.readdirSync(commandsDir).filter((f) => f.endsWith(".md")).length : 0;
606
652
  report("L1", "commands available", cmdCount >= expectedCommands ? "pass" : "warn", `${cmdCount}/${expectedCommands} command files`);
607
653
 
608
- // L2: Environment variables
609
- console.log("\nL2: Environment");
654
+ // L2: OpenCode provider configuration
655
+ console.log("\nL2: OpenCode Providers");
610
656
 
611
- const modelProfilePath = path.join(targetRoot, ".sdd", "model-profile.json");
612
- const modelProfile = fs.existsSync(modelProfilePath) ? JSON.parse(fs.readFileSync(modelProfilePath, "utf-8")) : null;
657
+ const sddConfigPath = path.join(globalConfigDir, "sdd-multiagent-opencode.json");
658
+ const sddConfig = readJsonSafe(sddConfigPath);
659
+ const globalOpencodeConfig = parseJsonc(path.join(globalConfigDir, "opencode.json"));
660
+ const nativeAuth = readJsonSafe(getNativeAuthPath());
613
661
 
614
- if (modelProfile) {
662
+ if (sddConfig) {
615
663
  const models = new Set();
616
- if (modelProfile.defaultPrimary) models.add(modelProfile.defaultPrimary);
617
- if (modelProfile.agents) {
618
- for (const m of Object.values(modelProfile.agents)) models.add(m);
664
+ if (sddConfig.model) models.add(sddConfig.model);
665
+ if (sddConfig.small_model) models.add(sddConfig.small_model);
666
+ if (sddConfig.agent) {
667
+ for (const agentConfig of Object.values(sddConfig.agent)) {
668
+ if (agentConfig?.model) models.add(agentConfig.model);
669
+ }
619
670
  }
620
671
 
621
- const providerChecks = [
622
- { name: "OPENAI_API_KEY", pattern: /openai|gpt|o1|o3/i },
623
- { name: "ANTHROPIC_API_KEY", pattern: /anthropic|claude/i },
624
- { name: "GOOGLE_API_KEY", pattern: /google|gemini/i },
625
- { name: "OPENCODE_ZEN_API_KEY", pattern: /zen/i },
626
- ];
627
-
628
- for (const { name, pattern } of providerChecks) {
629
- const modelMatch = [...models].some((m) => pattern.test(m));
630
- if (modelMatch) {
631
- const hasKey = !!process.env[name];
632
- report("L2", `${name}`, hasKey ? "pass" : "warn", hasKey ? "set" : "not set — models requiring this provider were detected");
633
- }
672
+ const providerIds = new Set([...models].map(getProviderId).filter(Boolean));
673
+
674
+ for (const providerId of providerIds) {
675
+ const configuredBy = describeProviderConfig(providerId, globalOpencodeConfig, nativeAuth);
676
+ report(
677
+ "L2",
678
+ `provider ${providerId}`,
679
+ configuredBy ? "pass" : "warn",
680
+ configuredBy ?? "model uses this provider but no native auth, opencode.json provider, or env key was found"
681
+ );
634
682
  }
635
683
  } else {
636
- report("L2", "model-profile.json", "warn", "not found — using default model routing");
684
+ report("L2", "sdd-multiagent-opencode.json", "warn", "not found in ~/.config/opencode — using built-in defaults");
637
685
  }
638
686
 
639
687
  // L3: Liveness probe (--deep only)
640
688
  if (args.flags.deep) {
641
689
  console.log("\nL3: Liveness Probe");
642
690
 
643
- const primaryModel = modelProfile?.defaultPrimary || process.env.DEFAULT_MODEL || "unknown";
691
+ const primaryModel = sddConfig?.model || process.env.DEFAULT_MODEL || "unknown";
644
692
  report("L3", "model endpoint", "warn", `would probe ${primaryModel} — liveness requires valid API credentials and network access`);
645
693
  report("L3", "note", "warn", "full liveness probe requires HTTP client — run manually with curl or your provider's dashboard");
646
694
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tudeorangbiasa/sdd-multiagent-opencode",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Spec-Driven Development workflow kit for OpenCode with 5 core commands, multi-agent support, CLI wrappers, and configurable model routing",
5
5
  "type": "module",
6
6
  "main": ".opencode/plugins/sdd-register.js",
@@ -15,6 +15,7 @@
15
15
  "vendor",
16
16
  "README.md",
17
17
  "GUIDE.md",
18
+ "sdd-multiagent-opencode.json",
18
19
  "opencode.json"
19
20
  ],
20
21
  "scripts": {
@@ -0,0 +1,50 @@
1
+ {
2
+ "model": "openai/gpt-5.5",
3
+ "small_model": "opencode/deepseek-v4-flash-free",
4
+ "agent": {
5
+ "sdd-orchestrator": {
6
+ "model": "openai/gpt-5.5"
7
+ },
8
+ "sdd-planner": {
9
+ "model": "openai/gpt-5.5"
10
+ },
11
+ "sdd-explorer": {
12
+ "model": "opencode/deepseek-v4-flash-free"
13
+ },
14
+ "sdd-implementer": {
15
+ "model": "opencode/deepseek-v4-flash-free"
16
+ },
17
+ "sdd-verifier": {
18
+ "model": "opencode/qwen3.6-plus-free"
19
+ },
20
+ "sdd-reviewer": {
21
+ "model": "opencode/minimax-m2.5-free"
22
+ },
23
+ "sdd-quick": {
24
+ "model": "opencode/qwen3.6-plus-free"
25
+ }
26
+ },
27
+ "reasoning": {
28
+ "default": "low",
29
+ "applyProviderOptions": true,
30
+ "resetToolOverrideAfterNextRequest": true,
31
+ "agents": {
32
+ "general": "medium",
33
+ "plan": "high",
34
+ "sdd-orchestrator": "high",
35
+ "sdd-planner": "high",
36
+ "sdd-explorer": "low",
37
+ "sdd-implementer": "medium",
38
+ "sdd-verifier": "medium",
39
+ "sdd-reviewer": "medium",
40
+ "sdd-quick": "low"
41
+ },
42
+ "commands": {
43
+ "sdd-explore": "medium",
44
+ "sdd-propose": "high",
45
+ "sdd-apply": "medium",
46
+ "sdd-ship": "medium",
47
+ "sdd-quick": "low"
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "agent": {
3
+ "compaction": {
4
+ "model": "opencode/minimax-m2.5-free",
5
+ "temperature": 0.1
6
+ }
7
+ }
8
+ }
@@ -1,9 +1,3 @@
1
1
  {
2
- "$schema": "https://opencode.ai/config.json",
3
- "agent": {
4
- "compaction": {
5
- "model": "opencode/minimax-m2.5-free",
6
- "temperature": 0.1
7
- }
8
- }
2
+ "$schema": "https://opencode.ai/config.json"
9
3
  }
@@ -1,31 +0,0 @@
1
- {
2
- "_info": {
3
- "description": "Model routing configuration. Edit this file to change which model each agent uses. Restart OpenCode after editing.",
4
- "requirement": "SDD requires at least ONE paid/subscription model for orchestrator and planner. These roles need strong reasoning that free models cannot provide.",
5
- "subscription_options": {
6
- "openai_plus": "GPT 5.5 with reasoning and multimodal",
7
- "opencode_go": "$10/month — includes DeepSeek V4 Pro, GLM 5.1, Kimi K2.6, MiniMax M2.7",
8
- "opencode_zen": "Pay-per-token, zero markup — access to Claude Opus 4.7, GPT 5.5, Gemini 3.1 Pro"
9
- },
10
- "recommendations": {
11
- "orchestrator": "PAID: GPT 5.5 (OpenAI) or DeepSeek V4 Pro (Go) — needs strong reasoning for DAG coordination",
12
- "planner": "PAID: GPT 5.5 (OpenAI), Claude Opus 4.7, or DeepSeek V4 Pro (Go) — needs maximum reasoning for architecture",
13
- "explorer": "FREE: DeepSeek v4 Flash — fast readonly exploration, token-efficient",
14
- "implementer": "FREE: DeepSeek v4 Flash — code generation, high token usage",
15
- "verifier": "FREE: Qwen3.6 Plus — multimodal for Chrome DevTools screenshots",
16
- "reviewer": "FREE: MiniMax M2.5 — structured code review output",
17
- "quick": "FREE: Qwen3.6 Plus or DeepSeek v4 Flash — minimal prompt for small edits"
18
- }
19
- },
20
- "defaultPrimary": "openai/gpt-5.5",
21
- "small": "opencode/deepseek-v4-flash-free",
22
- "agents": {
23
- "sdd-orchestrator": "openai/gpt-5.5",
24
- "sdd-planner": "openai/gpt-5.5",
25
- "sdd-explorer": "opencode/deepseek-v4-flash-free",
26
- "sdd-implementer": "opencode/deepseek-v4-flash-free",
27
- "sdd-verifier": "opencode/qwen3.6-plus-free",
28
- "sdd-reviewer": "opencode/minimax-m2.5-free",
29
- "sdd-quick": "opencode/qwen3.6-plus-free"
30
- }
31
- }
@@ -1,23 +0,0 @@
1
- {
2
- "default": "low",
3
- "applyProviderOptions": true,
4
- "resetToolOverrideAfterNextRequest": true,
5
- "agents": {
6
- "general": "medium",
7
- "plan": "high",
8
- "sdd-orchestrator": "high",
9
- "sdd-planner": "high",
10
- "sdd-explorer": "low",
11
- "sdd-implementer": "medium",
12
- "sdd-verifier": "medium",
13
- "sdd-reviewer": "medium",
14
- "sdd-quick": "low"
15
- },
16
- "commands": {
17
- "sdd-explore": "medium",
18
- "sdd-propose": "high",
19
- "sdd-apply": "medium",
20
- "sdd-ship": "medium",
21
- "sdd-quick": "low"
22
- }
23
- }