gsd-opencode 1.20.1 → 1.20.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.
Files changed (31) hide show
  1. package/commands/gsd/gsd-check-profile.md +30 -0
  2. package/commands/gsd/gsd-research-phase.md +2 -2
  3. package/get-shit-done/bin/gsd-oc-commands/check-oc-config-json.cjs +169 -0
  4. package/get-shit-done/bin/gsd-oc-commands/check-opencode-json.cjs +86 -0
  5. package/get-shit-done/bin/gsd-oc-commands/get-profile.cjs +117 -0
  6. package/get-shit-done/bin/gsd-oc-commands/set-profile.cjs +357 -0
  7. package/get-shit-done/bin/gsd-oc-commands/update-opencode-json.cjs +199 -0
  8. package/get-shit-done/bin/gsd-oc-commands/validate-models.cjs +75 -0
  9. package/get-shit-done/bin/gsd-oc-lib/oc-config.cjs +205 -0
  10. package/get-shit-done/bin/gsd-oc-lib/oc-core.cjs +113 -0
  11. package/get-shit-done/bin/gsd-oc-lib/oc-models.cjs +133 -0
  12. package/get-shit-done/bin/gsd-oc-lib/oc-profile-config.cjs +409 -0
  13. package/get-shit-done/bin/gsd-oc-tools.cjs +130 -0
  14. package/get-shit-done/bin/lib/oc-config.cjs +200 -0
  15. package/get-shit-done/bin/lib/oc-core.cjs +114 -0
  16. package/get-shit-done/bin/lib/oc-models.cjs +133 -0
  17. package/get-shit-done/bin/test/fixtures/oc-config-invalid.json +14 -0
  18. package/get-shit-done/bin/test/fixtures/oc-config-valid.json +22 -0
  19. package/get-shit-done/bin/test/get-profile.test.cjs +447 -0
  20. package/get-shit-done/bin/test/oc-profile-config.test.cjs +377 -0
  21. package/get-shit-done/bin/test/pivot-profile.test.cjs +276 -0
  22. package/get-shit-done/bin/test/set-profile.test.cjs +301 -0
  23. package/get-shit-done/workflows/diagnose-issues.md +1 -1
  24. package/get-shit-done/workflows/discuss-phase.md +1 -1
  25. package/get-shit-done/workflows/new-project.md +4 -4
  26. package/get-shit-done/workflows/oc-check-profile.md +181 -0
  27. package/get-shit-done/workflows/oc-set-profile.md +83 -243
  28. package/get-shit-done/workflows/plan-phase.md +4 -4
  29. package/get-shit-done/workflows/quick.md +1 -1
  30. package/get-shit-done/workflows/settings.md +4 -3
  31. package/package.json +2 -2
@@ -0,0 +1,200 @@
1
+ /**
2
+ * oc-config.cjs — Profile configuration operations for gsd-oc-tools CLI
3
+ *
4
+ * Provides functions for loading profile config and applying model assignments to opencode.json.
5
+ * Follows gsd-tools.cjs architecture pattern.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * Valid profile types whitelist
13
+ */
14
+ const VALID_PROFILES = ['simple', 'smart', 'genius'];
15
+
16
+ /**
17
+ * Profile to agent mapping
18
+ * Maps profile keys to opencode.json agent names
19
+ */
20
+ const PROFILE_AGENT_MAPPING = {
21
+ // Planning agents
22
+ planning: [
23
+ 'gsd-planner',
24
+ 'gsd-plan-checker',
25
+ 'gsd-phase-researcher',
26
+ 'gsd-roadmapper',
27
+ 'gsd-project-researcher',
28
+ 'gsd-research-synthesizer',
29
+ 'gsd-codebase-mapper'
30
+ ],
31
+ // Execution agents
32
+ execution: [
33
+ 'gsd-executor',
34
+ 'gsd-debugger'
35
+ ],
36
+ // Verification agents
37
+ verification: [
38
+ 'gsd-verifier',
39
+ 'gsd-integration-checker'
40
+ ]
41
+ };
42
+
43
+ /**
44
+ * Load profile configuration from .planning/config.json
45
+ *
46
+ * @param {string} cwd - Current working directory
47
+ * @returns {Object|null} Parsed config object or null on error
48
+ */
49
+ function loadProfileConfig(cwd) {
50
+ try {
51
+ const configPath = path.join(cwd, '.planning', 'config.json');
52
+
53
+ if (!fs.existsSync(configPath)) {
54
+ return null;
55
+ }
56
+
57
+ const content = fs.readFileSync(configPath, 'utf8');
58
+ const config = JSON.parse(content);
59
+
60
+ return config;
61
+ } catch (err) {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Apply profile configuration to opencode.json
68
+ * Updates agent model assignments based on profile
69
+ *
70
+ * @param {string} opencodePath - Path to opencode.json
71
+ * @param {string} configPath - Path to .planning/config.json
72
+ * @returns {Object} {success: true, updated: [agentNames]} or {success: false, error: {code, message}}
73
+ */
74
+ function applyProfileToOpencode(opencodePath, configPath) {
75
+ try {
76
+ // Load profile config
77
+ let config;
78
+ if (fs.existsSync(configPath)) {
79
+ const content = fs.readFileSync(configPath, 'utf8');
80
+ config = JSON.parse(content);
81
+ } else {
82
+ return {
83
+ success: false,
84
+ error: {
85
+ code: 'CONFIG_NOT_FOUND',
86
+ message: `.planning/config.json not found at ${configPath}`
87
+ }
88
+ };
89
+ }
90
+
91
+ // Validate profile_type
92
+ const profileType = config.profile_type || config.profiles?.profile_type;
93
+ if (!profileType) {
94
+ return {
95
+ success: false,
96
+ error: {
97
+ code: 'PROFILE_NOT_FOUND',
98
+ message: 'profile_type not found in config.json'
99
+ }
100
+ };
101
+ }
102
+
103
+ if (!VALID_PROFILES.includes(profileType)) {
104
+ return {
105
+ success: false,
106
+ error: {
107
+ code: 'INVALID_PROFILE',
108
+ message: `Invalid profile_type: "${profileType}". Valid profiles: ${VALID_PROFILES.join(', ')}`
109
+ }
110
+ };
111
+ }
112
+
113
+ // Load opencode.json
114
+ if (!fs.existsSync(opencodePath)) {
115
+ return {
116
+ success: false,
117
+ error: {
118
+ code: 'CONFIG_NOT_FOUND',
119
+ message: `opencode.json not found at ${opencodePath}`
120
+ }
121
+ };
122
+ }
123
+
124
+ const opencodeContent = fs.readFileSync(opencodePath, 'utf8');
125
+ const opencodeData = JSON.parse(opencodeContent);
126
+
127
+ // Get model assignments from profile
128
+ // Support both structures: profiles.planning or profiles.models.planning
129
+ const profiles = config.profiles || {};
130
+ let profileModels;
131
+
132
+ // Try new structure first: profiles.models.{planning|execution|verification}
133
+ if (profiles.models && typeof profiles.models === 'object') {
134
+ profileModels = profiles.models;
135
+ } else {
136
+ // Fallback to old structure: profiles.{planning|execution|verification}
137
+ profileModels = profiles[profileType] || {};
138
+ }
139
+
140
+ if (!profileModels.planning && !profileModels.execution && !profileModels.verification) {
141
+ return {
142
+ success: false,
143
+ error: {
144
+ code: 'PROFILE_NOT_FOUND',
145
+ message: `No model assignments found for profile "${profileType}"`
146
+ }
147
+ };
148
+ }
149
+
150
+ // Apply model assignments to agents
151
+ const updatedAgents = [];
152
+
153
+ // Initialize agent object if it doesn't exist
154
+ if (!opencodeData.agent) {
155
+ opencodeData.agent = {};
156
+ }
157
+
158
+ // Apply each profile category
159
+ for (const [category, agentNames] of Object.entries(PROFILE_AGENT_MAPPING)) {
160
+ const modelId = profileModels[category];
161
+
162
+ if (modelId) {
163
+ for (const agentName of agentNames) {
164
+ // Handle both string and object agent configurations
165
+ if (typeof opencodeData.agent[agentName] === 'string') {
166
+ opencodeData.agent[agentName] = modelId;
167
+ } else if (typeof opencodeData.agent[agentName] === 'object' && opencodeData.agent[agentName] !== null) {
168
+ opencodeData.agent[agentName].model = modelId;
169
+ } else {
170
+ opencodeData.agent[agentName] = modelId;
171
+ }
172
+ updatedAgents.push(agentName);
173
+ }
174
+ }
175
+ }
176
+
177
+ // write updated opencode.json
178
+ fs.writeFileSync(opencodePath, JSON.stringify(opencodeData, null, 2) + '\n', 'utf8');
179
+
180
+ return {
181
+ success: true,
182
+ updated: updatedAgents
183
+ };
184
+ } catch (err) {
185
+ return {
186
+ success: false,
187
+ error: {
188
+ code: 'UPDATE_FAILED',
189
+ message: `Failed to apply profile: ${err.message}`
190
+ }
191
+ };
192
+ }
193
+ }
194
+
195
+ module.exports = {
196
+ loadProfileConfig,
197
+ applyProfileToOpencode,
198
+ VALID_PROFILES,
199
+ PROFILE_AGENT_MAPPING
200
+ };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * oc-core.cjs — Shared utilities for gsd-oc-tools CLI
3
+ *
4
+ * Provides common functions for output formatting, error handling, file operations.
5
+ * Follows gsd-tools.cjs architecture pattern.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * Output result in JSON envelope format
13
+ * Large payloads (>50KB) are written to temp file with @file: prefix
14
+ *
15
+ * @param {Object} result - The result data to output
16
+ * @param {boolean} raw - If true, output raw value instead of envelope
17
+ * @param {*} rawValue - The raw value to output if raw=true
18
+ */
19
+ function output(result, raw = false, rawValue = null) {
20
+ let outputData;
21
+
22
+ if (raw && rawValue !== null) {
23
+ outputData = rawValue;
24
+ } else {
25
+ outputData = result;
26
+ }
27
+
28
+ const outputStr = JSON.stringify(outputData, null, 2);
29
+
30
+ // Large payload handling (>50KB)
31
+ if (outputStr.length > 50 * 1024) {
32
+ const tempFile = path.join(require('os').tmpdir(), `gsd-oc-${Date.now()}.json`);
33
+ fs.writeFileSync(tempFile, outputStr, 'utf8');
34
+ console.log(`@file:${tempFile}`);
35
+ } else {
36
+ console.log(outputStr);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Output error in standardized envelope format to stderr
42
+ *
43
+ * @param {string} message - Error message
44
+ * @param {string} code - Error code (e.g., 'CONFIG_NOT_FOUND', 'INVALID_JSON')
45
+ */
46
+ function error(message, code = 'UNKNOWN_ERROR') {
47
+ const errorEnvelope = {
48
+ success: false,
49
+ error: {
50
+ code,
51
+ message
52
+ }
53
+ };
54
+ console.error(JSON.stringify(errorEnvelope, null, 2));
55
+ process.exit(1);
56
+ }
57
+
58
+ /**
59
+ * Safely read a file, returning null on failure
60
+ *
61
+ * @param {string} filePath - Path to file
62
+ * @returns {string|null} File contents or null
63
+ */
64
+ function safeReadFile(filePath) {
65
+ try {
66
+ return fs.readFileSync(filePath, 'utf8');
67
+ } catch (err) {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Create timestamped backup of a file
74
+ *
75
+ * @param {string} filePath - Path to file to backup
76
+ * @param {string} backupDir - Directory for backups (.opencode-backups/)
77
+ * @returns {string|null} Backup file path or null on failure
78
+ */
79
+ function createBackup(filePath, backupDir = '.opencode-backups') {
80
+ try {
81
+ // Ensure backup directory exists
82
+ if (!fs.existsSync(backupDir)) {
83
+ fs.mkdirSync(backupDir, { recursive: true });
84
+ }
85
+
86
+ // read original file
87
+ const content = fs.readFileSync(filePath, 'utf8');
88
+
89
+ // Create timestamped filename (YYYYMMDD-HHmmss-SSS format)
90
+ const now = new Date();
91
+ const timestamp = now.toISOString()
92
+ .replace(/[-:T]/g, '')
93
+ .replace(/\.\d{3}Z$/, '')
94
+ .replace(/(\d{8})(\d{6})(\d{3})/, '$1-$2-$3');
95
+
96
+ const fileName = path.basename(filePath);
97
+ const backupFileName = `${timestamp}-${fileName}`;
98
+ const backupPath = path.join(backupDir, backupFileName);
99
+
100
+ // write backup
101
+ fs.writeFileSync(backupPath, content, 'utf8');
102
+
103
+ return backupPath;
104
+ } catch (err) {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ module.exports = {
110
+ output,
111
+ error,
112
+ safeReadFile,
113
+ createBackup
114
+ };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * oc-models.cjs — Model catalog operations for gsd-oc-tools CLI
3
+ *
4
+ * Provides functions for fetching and validating model IDs against opencode models output.
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { execSync } = require('child_process');
10
+
11
+ /**
12
+ * Fetch model catalog from opencode models command
13
+ *
14
+ * @returns {Object} {success: boolean, models: string[]} or {success: false, error: {...}}
15
+ */
16
+ function getModelCatalog() {
17
+ try {
18
+ const output = execSync('opencode models', {
19
+ encoding: 'utf8',
20
+ stdio: ['pipe', 'pipe', 'pipe']
21
+ });
22
+
23
+ // Parse output (one model per line)
24
+ const models = output
25
+ .split('\n')
26
+ .map(line => line.trim())
27
+ .filter(line => line.length > 0);
28
+
29
+ return {
30
+ success: true,
31
+ models
32
+ };
33
+ } catch (err) {
34
+ return {
35
+ success: false,
36
+ error: {
37
+ code: 'FETCH_FAILED',
38
+ message: `Failed to fetch model catalog: ${err.message}`
39
+ }
40
+ };
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Validate model IDs in opencode.json against valid models list
46
+ *
47
+ * @param {string} opencodePath - Path to opencode.json file
48
+ * @param {string[]} validModels - Array of valid model IDs
49
+ * @returns {Object} {valid, total, validCount, invalidCount, issues: [{agent, model, reason}]}
50
+ */
51
+ function validateModelIds(opencodePath, validModels) {
52
+ const issues = [];
53
+ let total = 0;
54
+ let validCount = 0;
55
+ let invalidCount = 0;
56
+
57
+ try {
58
+ const content = fs.readFileSync(opencodePath, 'utf8');
59
+ const opencodeData = JSON.parse(content);
60
+
61
+ // Look for agent model assignments
62
+ // Common patterns: agent.model, profiles.*.model, models.*
63
+ const assignments = [];
64
+
65
+ // Check for agents at root level
66
+ if (opencodeData.agent && typeof opencodeData.agent === 'object') {
67
+ Object.entries(opencodeData.agent).forEach(([agentName, config]) => {
68
+ if (typeof config === 'string') {
69
+ assignments.push({ agent: `agent.${agentName}`, model: config });
70
+ } else if (config && typeof config === 'object' && config.model) {
71
+ assignments.push({ agent: `agent.${agentName}`, model: config.model });
72
+ }
73
+ });
74
+ }
75
+
76
+ // Check for profiles
77
+ if (opencodeData.profiles && typeof opencodeData.profiles === 'object') {
78
+ Object.entries(opencodeData.profiles).forEach(([profileName, config]) => {
79
+ if (config && typeof config === 'object') {
80
+ Object.entries(config).forEach(([key, value]) => {
81
+ if (key.includes('model') && typeof value === 'string') {
82
+ assignments.push({ agent: `profiles.${profileName}.${key}`, model: value });
83
+ }
84
+ });
85
+ }
86
+ });
87
+ }
88
+
89
+ // Check for models at root level
90
+ if (opencodeData.models && typeof opencodeData.models === 'object') {
91
+ Object.entries(opencodeData.models).forEach(([modelName, modelId]) => {
92
+ if (typeof modelId === 'string') {
93
+ assignments.push({ agent: `models.${modelName}`, model: modelId });
94
+ }
95
+ });
96
+ }
97
+
98
+ // Validate each assignment
99
+ total = assignments.length;
100
+ for (const { agent, model } of assignments) {
101
+ if (validModels.includes(model)) {
102
+ validCount++;
103
+ } else {
104
+ invalidCount++;
105
+ issues.push({
106
+ agent,
107
+ model,
108
+ reason: 'Model ID not found in opencode models catalog'
109
+ });
110
+ }
111
+ }
112
+
113
+ return {
114
+ valid: invalidCount === 0,
115
+ total,
116
+ validCount,
117
+ invalidCount,
118
+ issues
119
+ };
120
+ } catch (err) {
121
+ if (err.code === 'ENOENT') {
122
+ throw new Error('CONFIG_NOT_FOUND');
123
+ } else if (err instanceof SyntaxError) {
124
+ throw new Error('INVALID_JSON');
125
+ }
126
+ throw err;
127
+ }
128
+ }
129
+
130
+ module.exports = {
131
+ getModelCatalog,
132
+ validateModelIds
133
+ };
@@ -0,0 +1,14 @@
1
+ {
2
+ "profiles": {
3
+ "presets": {
4
+ "incomplete": {
5
+ "planning": "bailian-coding-plan/qwen3.5-plus"
6
+ },
7
+ "invalid-models": {
8
+ "planning": "invalid-model-xyz-12345",
9
+ "execution": "another-invalid-model-67890",
10
+ "verification": "yet-another-invalid-abcde"
11
+ }
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "profiles": {
3
+ "presets": {
4
+ "simple": {
5
+ "planning": "bailian-coding-plan/qwen3.5-plus",
6
+ "execution": "bailian-coding-plan/qwen3.5-plus",
7
+ "verification": "bailian-coding-plan/qwen3.5-plus"
8
+ },
9
+ "smart": {
10
+ "planning": "bailian-coding-plan/qwen3.5-plus",
11
+ "execution": "bailian-coding-plan/qwen3.5-plus",
12
+ "verification": "bailian-coding-plan/qwen3.5-plus"
13
+ },
14
+ "genius": {
15
+ "planning": "bailian-coding-plan/qwen3.5-plus",
16
+ "execution": "bailian-coding-plan/qwen3.5-plus",
17
+ "verification": "bailian-coding-plan/qwen3.5-plus"
18
+ }
19
+ }
20
+ },
21
+ "current_oc_profile": "smart"
22
+ }