gsd-opencode 1.20.3 → 1.20.4
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.
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* allow-read-config.cjs — Add external_directory permission to read GSD config folder
|
|
3
|
+
*
|
|
4
|
+
* Creates or updates local opencode.json with permission to access:
|
|
5
|
+
* ~/.config/opencode/get-shit-done/
|
|
6
|
+
*
|
|
7
|
+
* This allows gsd-opencode commands to read workflow files, templates, and
|
|
8
|
+
* configuration from the global GSD installation directory.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node allow-read-config.cjs # Add read permission
|
|
12
|
+
* node allow-read-config.cjs --dry-run # Preview changes
|
|
13
|
+
* node allow-read-config.cjs --verbose # Verbose output
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const { output, error, createBackup } = require('../gsd-oc-lib/oc-core.cjs');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Error codes for allow-read-config operations
|
|
23
|
+
*/
|
|
24
|
+
const ERROR_CODES = {
|
|
25
|
+
WRITE_FAILED: 'WRITE_FAILED',
|
|
26
|
+
APPLY_FAILED: 'APPLY_FAILED',
|
|
27
|
+
ROLLBACK_FAILED: 'ROLLBACK_FAILED',
|
|
28
|
+
INVALID_ARGS: 'INVALID_ARGS'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the GSD config directory path
|
|
33
|
+
* Uses environment variable if set, otherwise defaults to ~/.config/opencode/get-shit-done
|
|
34
|
+
*
|
|
35
|
+
* @returns {string} GSD config directory path
|
|
36
|
+
*/
|
|
37
|
+
function getGsdConfigDir() {
|
|
38
|
+
const envDir = process.env.OPENCODE_CONFIG_DIR;
|
|
39
|
+
if (envDir) {
|
|
40
|
+
return envDir;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const homeDir = os.homedir();
|
|
44
|
+
return path.join(homeDir, '.config', 'opencode', 'get-shit-done');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build the external_directory permission pattern
|
|
49
|
+
*
|
|
50
|
+
* @param {string} gsdDir - GSD config directory
|
|
51
|
+
* @returns {string} Permission pattern with wildcard
|
|
52
|
+
*/
|
|
53
|
+
function buildPermissionPattern(gsdDir) {
|
|
54
|
+
// Use ** for recursive matching (all subdirectories and files)
|
|
55
|
+
return `${gsdDir}/**`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if permission already exists in opencode.json
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} opencodeData - Parsed opencode.json content
|
|
62
|
+
* @param {string} pattern - Permission pattern to check
|
|
63
|
+
* @returns {boolean} True if permission exists
|
|
64
|
+
*/
|
|
65
|
+
function permissionExists(opencodeData, pattern) {
|
|
66
|
+
const permissions = opencodeData.permission;
|
|
67
|
+
|
|
68
|
+
if (!permissions) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const externalDirPerms = permissions.external_directory;
|
|
73
|
+
if (!externalDirPerms || typeof externalDirPerms !== 'object') {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if the pattern exists and is set to "allow"
|
|
78
|
+
return externalDirPerms[pattern] === 'allow';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Main command function
|
|
83
|
+
*
|
|
84
|
+
* @param {string} cwd - Current working directory
|
|
85
|
+
* @param {string[]} args - Command line arguments
|
|
86
|
+
*/
|
|
87
|
+
function allowReadConfig(cwd, args) {
|
|
88
|
+
const verbose = args.includes('--verbose');
|
|
89
|
+
const dryRun = args.includes('--dry-run');
|
|
90
|
+
const raw = args.includes('--raw');
|
|
91
|
+
|
|
92
|
+
const log = verbose ? (...args) => console.error('[allow-read-config]', ...args) : () => {};
|
|
93
|
+
|
|
94
|
+
const opencodePath = path.join(cwd, 'opencode.json');
|
|
95
|
+
const backupsDir = path.join(cwd, '.planning', 'backups');
|
|
96
|
+
const gsdConfigDir = getGsdConfigDir();
|
|
97
|
+
const permissionPattern = buildPermissionPattern(gsdConfigDir);
|
|
98
|
+
|
|
99
|
+
log('Starting allow-read-config command');
|
|
100
|
+
log(`GSD config directory: ${gsdConfigDir}`);
|
|
101
|
+
log(`Permission pattern: ${permissionPattern}`);
|
|
102
|
+
|
|
103
|
+
// Check for invalid arguments
|
|
104
|
+
const validFlags = ['--verbose', '--dry-run', '--raw'];
|
|
105
|
+
const invalidArgs = args.filter(arg =>
|
|
106
|
+
arg.startsWith('--') && !validFlags.includes(arg)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (invalidArgs.length > 0) {
|
|
110
|
+
error(`Unknown arguments: ${invalidArgs.join(', ')}`, 'INVALID_ARGS');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Load or create opencode.json
|
|
114
|
+
let opencodeData;
|
|
115
|
+
let fileExisted = false;
|
|
116
|
+
|
|
117
|
+
if (fs.existsSync(opencodePath)) {
|
|
118
|
+
try {
|
|
119
|
+
const content = fs.readFileSync(opencodePath, 'utf8');
|
|
120
|
+
opencodeData = JSON.parse(content);
|
|
121
|
+
fileExisted = true;
|
|
122
|
+
log('Loaded existing opencode.json');
|
|
123
|
+
} catch (err) {
|
|
124
|
+
error(`Failed to parse opencode.json: ${err.message}`, 'INVALID_JSON');
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
// Create initial opencode.json structure
|
|
128
|
+
opencodeData = {
|
|
129
|
+
"$schema": "https://opencode.ai/config.json"
|
|
130
|
+
};
|
|
131
|
+
log('Creating new opencode.json');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check if permission already exists
|
|
135
|
+
const exists = permissionExists(opencodeData, permissionPattern);
|
|
136
|
+
|
|
137
|
+
if (exists) {
|
|
138
|
+
log('Permission already exists');
|
|
139
|
+
output({
|
|
140
|
+
success: true,
|
|
141
|
+
data: {
|
|
142
|
+
dryRun: dryRun,
|
|
143
|
+
action: 'permission_exists',
|
|
144
|
+
pattern: permissionPattern,
|
|
145
|
+
message: 'Permission already configured'
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Dry-run mode - preview changes
|
|
152
|
+
if (dryRun) {
|
|
153
|
+
log('Dry-run mode - no changes will be made');
|
|
154
|
+
|
|
155
|
+
const changes = [];
|
|
156
|
+
if (!fileExisted) {
|
|
157
|
+
changes.push('Create opencode.json');
|
|
158
|
+
}
|
|
159
|
+
changes.push(`Add external_directory permission: ${permissionPattern}`);
|
|
160
|
+
|
|
161
|
+
output({
|
|
162
|
+
success: true,
|
|
163
|
+
data: {
|
|
164
|
+
dryRun: true,
|
|
165
|
+
action: 'add_permission',
|
|
166
|
+
pattern: permissionPattern,
|
|
167
|
+
gsdConfigDir: gsdConfigDir,
|
|
168
|
+
changes: changes,
|
|
169
|
+
message: fileExisted ? 'Would update opencode.json' : 'Would create opencode.json'
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Create backup if file exists
|
|
176
|
+
let backupPath = null;
|
|
177
|
+
if (fileExisted) {
|
|
178
|
+
// Ensure backup directory exists
|
|
179
|
+
if (!fs.existsSync(backupsDir)) {
|
|
180
|
+
fs.mkdirSync(backupsDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
backupPath = createBackup(opencodePath, backupsDir);
|
|
184
|
+
log(`Backup created: ${backupPath}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Initialize permission structure if needed
|
|
188
|
+
if (!opencodeData.permission) {
|
|
189
|
+
opencodeData.permission = {};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!opencodeData.permission.external_directory) {
|
|
193
|
+
opencodeData.permission.external_directory = {};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Add the permission
|
|
197
|
+
opencodeData.permission.external_directory[permissionPattern] = 'allow';
|
|
198
|
+
|
|
199
|
+
log('Permission added to opencode.json');
|
|
200
|
+
|
|
201
|
+
// Write updated opencode.json
|
|
202
|
+
try {
|
|
203
|
+
fs.writeFileSync(opencodePath, JSON.stringify(opencodeData, null, 2) + '\n', 'utf8');
|
|
204
|
+
log('Updated opencode.json');
|
|
205
|
+
} catch (err) {
|
|
206
|
+
// Rollback if backup exists
|
|
207
|
+
if (backupPath) {
|
|
208
|
+
try {
|
|
209
|
+
fs.copyFileSync(backupPath, opencodePath);
|
|
210
|
+
} catch (rollbackErr) {
|
|
211
|
+
error(
|
|
212
|
+
`Failed to write opencode.json AND failed to rollback: ${rollbackErr.message}`,
|
|
213
|
+
'ROLLBACK_FAILED'
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
error(`Failed to write opencode.json: ${err.message}`, 'WRITE_FAILED');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
output({
|
|
221
|
+
success: true,
|
|
222
|
+
data: {
|
|
223
|
+
action: 'add_permission',
|
|
224
|
+
pattern: permissionPattern,
|
|
225
|
+
gsdConfigDir: gsdConfigDir,
|
|
226
|
+
opencodePath: opencodePath,
|
|
227
|
+
backup: backupPath,
|
|
228
|
+
created: !fileExisted,
|
|
229
|
+
message: fileExisted ? 'opencode.json updated' : 'opencode.json created'
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
process.exit(0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = allowReadConfig;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* validate-models Validate model IDs against opencode catalog
|
|
17
17
|
* set-profile Switch profile with interactive model selection
|
|
18
18
|
* get-profile Get current profile or specific profile from oc_config.json
|
|
19
|
+
* allow-read-config Add external_directory permission to read GSD config folder
|
|
19
20
|
* help Show this help message
|
|
20
21
|
*/
|
|
21
22
|
|
|
@@ -50,12 +51,13 @@ Available Commands:
|
|
|
50
51
|
validate-models Validate one or more model IDs against opencode catalog
|
|
51
52
|
set-profile Switch profile with interactive model selection wizard
|
|
52
53
|
get-profile Get current profile or specific profile from oc_config.json
|
|
54
|
+
allow-read-config Add external_directory permission to read ~/.config/opencode/get-shit-done/**
|
|
53
55
|
help Show this help message
|
|
54
56
|
|
|
55
57
|
Options:
|
|
56
58
|
--verbose Enable verbose output (stderr)
|
|
57
|
-
--raw Output raw
|
|
58
|
-
--dry-run Preview changes without applying (update-opencode-json)
|
|
59
|
+
--raw Output raw value instead of JSON envelope
|
|
60
|
+
--dry-run Preview changes without applying (update-opencode-json, allow-read-config)
|
|
59
61
|
|
|
60
62
|
Examples:
|
|
61
63
|
node gsd-oc-tools.cjs check-opencode-json
|
|
@@ -66,6 +68,8 @@ Examples:
|
|
|
66
68
|
node gsd-oc-tools.cjs get-profile
|
|
67
69
|
node gsd-oc-tools.cjs get-profile genius
|
|
68
70
|
node gsd-oc-tools.cjs get-profile --raw
|
|
71
|
+
node gsd-oc-tools.cjs allow-read-config
|
|
72
|
+
node gsd-oc-tools.cjs allow-read-config --dry-run
|
|
69
73
|
`.trim();
|
|
70
74
|
|
|
71
75
|
console.log(helpText);
|
|
@@ -121,9 +125,11 @@ switch (command) {
|
|
|
121
125
|
break;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
case 'allow-read-config': {
|
|
129
|
+
const allowReadConfig = require('./gsd-oc-commands/allow-read-config.cjs');
|
|
130
|
+
allowReadConfig(cwd, flags);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
127
133
|
|
|
128
134
|
default:
|
|
129
135
|
error(`Unknown command: ${command}\nRun 'node gsd-oc-tools.cjs help' for available commands.`);
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* allow-read-config.test.cjs — Tests for allow-read-config command
|
|
3
|
+
*
|
|
4
|
+
* Tests the allow-read-config command functionality:
|
|
5
|
+
* - Permission creation
|
|
6
|
+
* - Idempotency (detecting existing permission)
|
|
7
|
+
* - Dry-run mode
|
|
8
|
+
* - Backup creation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const CLI_PATH = path.join(__dirname, '../gsd-oc-commands/allow-read-config.cjs');
|
|
17
|
+
const TOOLS_PATH = path.join(__dirname, '../gsd-oc-tools.cjs');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a temporary test directory
|
|
21
|
+
*/
|
|
22
|
+
function createTestDir() {
|
|
23
|
+
const testDir = path.join(os.tmpdir(), `gsd-oc-test-${Date.now()}`);
|
|
24
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
25
|
+
return testDir;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Clean up test directory
|
|
30
|
+
*/
|
|
31
|
+
function cleanupTestDir(testDir) {
|
|
32
|
+
if (fs.existsSync(testDir)) {
|
|
33
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Run CLI command and parse JSON output
|
|
39
|
+
*/
|
|
40
|
+
function runCLI(testDir, args) {
|
|
41
|
+
const cmd = `node ${TOOLS_PATH} allow-read-config ${args.join(' ')}`;
|
|
42
|
+
const output = execSync(cmd, { cwd: testDir, encoding: 'utf8' });
|
|
43
|
+
return JSON.parse(output);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Test: Create new opencode.json with permission
|
|
48
|
+
*/
|
|
49
|
+
function testCreatePermission() {
|
|
50
|
+
console.log('Test: Create new opencode.json with permission...');
|
|
51
|
+
|
|
52
|
+
const testDir = createTestDir();
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const result = runCLI(testDir, []);
|
|
56
|
+
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
throw new Error(`Expected success, got: ${JSON.stringify(result)}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (result.data.action !== 'add_permission') {
|
|
62
|
+
throw new Error(`Expected action 'add_permission', got: ${result.data.action}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (result.data.created !== true) {
|
|
66
|
+
throw new Error(`Expected created=true, got: ${result.data.created}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Verify opencode.json was created
|
|
70
|
+
const opencodePath = path.join(testDir, 'opencode.json');
|
|
71
|
+
if (!fs.existsSync(opencodePath)) {
|
|
72
|
+
throw new Error('opencode.json was not created');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const content = JSON.parse(fs.readFileSync(opencodePath, 'utf8'));
|
|
76
|
+
if (!content.permission?.external_directory) {
|
|
77
|
+
throw new Error('Permission not added to opencode.json');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('✓ PASS: Create permission\n');
|
|
81
|
+
return true;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
84
|
+
return false;
|
|
85
|
+
} finally {
|
|
86
|
+
cleanupTestDir(testDir);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Test: Idempotency - detect existing permission
|
|
92
|
+
*/
|
|
93
|
+
function testIdempotency() {
|
|
94
|
+
console.log('Test: Idempotency (detect existing permission)...');
|
|
95
|
+
|
|
96
|
+
const testDir = createTestDir();
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// First call - create permission
|
|
100
|
+
runCLI(testDir, []);
|
|
101
|
+
|
|
102
|
+
// Second call - should detect existing
|
|
103
|
+
const result = runCLI(testDir, []);
|
|
104
|
+
|
|
105
|
+
if (!result.success) {
|
|
106
|
+
throw new Error(`Expected success, got: ${JSON.stringify(result)}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (result.data.action !== 'permission_exists') {
|
|
110
|
+
throw new Error(`Expected action 'permission_exists', got: ${result.data.action}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log('✓ PASS: Idempotency\n');
|
|
114
|
+
return true;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
117
|
+
return false;
|
|
118
|
+
} finally {
|
|
119
|
+
cleanupTestDir(testDir);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Test: Dry-run mode
|
|
125
|
+
*/
|
|
126
|
+
function testDryRun() {
|
|
127
|
+
console.log('Test: Dry-run mode...');
|
|
128
|
+
|
|
129
|
+
const testDir = createTestDir();
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const result = runCLI(testDir, ['--dry-run']);
|
|
133
|
+
|
|
134
|
+
if (!result.success) {
|
|
135
|
+
throw new Error(`Expected success, got: ${JSON.stringify(result)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (result.data.dryRun !== true) {
|
|
139
|
+
throw new Error(`Expected dryRun=true, got: ${result.data.dryRun}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Verify opencode.json was NOT created
|
|
143
|
+
const opencodePath = path.join(testDir, 'opencode.json');
|
|
144
|
+
if (fs.existsSync(opencodePath)) {
|
|
145
|
+
throw new Error('opencode.json should not be created in dry-run mode');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log('✓ PASS: Dry-run mode\n');
|
|
149
|
+
return true;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
152
|
+
return false;
|
|
153
|
+
} finally {
|
|
154
|
+
cleanupTestDir(testDir);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Test: Backup creation on update
|
|
160
|
+
*/
|
|
161
|
+
function testBackupCreation() {
|
|
162
|
+
console.log('Test: Backup creation on update...');
|
|
163
|
+
|
|
164
|
+
const testDir = createTestDir();
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Create initial opencode.json
|
|
168
|
+
const opencodePath = path.join(testDir, 'opencode.json');
|
|
169
|
+
const initialContent = {
|
|
170
|
+
"$schema": "https://opencode.ai/config.json",
|
|
171
|
+
"model": "test/model"
|
|
172
|
+
};
|
|
173
|
+
fs.writeFileSync(opencodePath, JSON.stringify(initialContent, null, 2) + '\n');
|
|
174
|
+
|
|
175
|
+
// Run allow-read-config
|
|
176
|
+
const result = runCLI(testDir, []);
|
|
177
|
+
|
|
178
|
+
if (!result.success) {
|
|
179
|
+
throw new Error(`Expected success, got: ${JSON.stringify(result)}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!result.data.backup) {
|
|
183
|
+
throw new Error('Expected backup path, got none');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!fs.existsSync(result.data.backup)) {
|
|
187
|
+
throw new Error(`Backup file does not exist: ${result.data.backup}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Verify backup content matches original
|
|
191
|
+
const backupContent = JSON.parse(fs.readFileSync(result.data.backup, 'utf8'));
|
|
192
|
+
if (JSON.stringify(backupContent) !== JSON.stringify(initialContent)) {
|
|
193
|
+
throw new Error('Backup content does not match original');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log('✓ PASS: Backup creation\n');
|
|
197
|
+
return true;
|
|
198
|
+
} catch (err) {
|
|
199
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
200
|
+
return false;
|
|
201
|
+
} finally {
|
|
202
|
+
cleanupTestDir(testDir);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Test: Verbose output
|
|
208
|
+
*/
|
|
209
|
+
function testVerbose() {
|
|
210
|
+
console.log('Test: Verbose output...');
|
|
211
|
+
|
|
212
|
+
const testDir = createTestDir();
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const cmd = `node ${TOOLS_PATH} allow-read-config --verbose`;
|
|
216
|
+
const output = execSync(cmd, { cwd: testDir, encoding: 'utf8', stdio: 'pipe' });
|
|
217
|
+
|
|
218
|
+
// Verbose output should contain log messages to stderr
|
|
219
|
+
// We just verify it doesn't crash
|
|
220
|
+
console.log('✓ PASS: Verbose output\n');
|
|
221
|
+
return true;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
224
|
+
return false;
|
|
225
|
+
} finally {
|
|
226
|
+
cleanupTestDir(testDir);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Run all tests
|
|
232
|
+
*/
|
|
233
|
+
function runTests() {
|
|
234
|
+
console.log('Running allow-read-config tests...\n');
|
|
235
|
+
console.log('=' .repeat(50));
|
|
236
|
+
console.log();
|
|
237
|
+
|
|
238
|
+
const results = [
|
|
239
|
+
testCreatePermission(),
|
|
240
|
+
testIdempotency(),
|
|
241
|
+
testDryRun(),
|
|
242
|
+
testBackupCreation(),
|
|
243
|
+
testVerbose()
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const passed = results.filter(r => r).length;
|
|
247
|
+
const total = results.length;
|
|
248
|
+
|
|
249
|
+
console.log('=' .repeat(50));
|
|
250
|
+
console.log(`Results: ${passed}/${total} tests passed`);
|
|
251
|
+
|
|
252
|
+
if (passed === total) {
|
|
253
|
+
console.log('✓ All tests passed!\n');
|
|
254
|
+
process.exit(0);
|
|
255
|
+
} else {
|
|
256
|
+
console.error(`✗ ${total - passed} test(s) failed\n`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Run tests
|
|
262
|
+
runTests();
|
|
@@ -5,6 +5,7 @@ You are executing the `/gsd-set-profile` command. Switch the project's active mo
|
|
|
5
5
|
This command reads/writes:
|
|
6
6
|
- `.planning/oc_config.json` — source of truth for profile state (profile_type, stage-to-model mapping)
|
|
7
7
|
- `opencode.json` — agent model assignments (derived from profile; updated automatically by CLI)
|
|
8
|
+
- `opencode.json` — external_directory permissions for reading GSD config folder (added automatically)
|
|
8
9
|
|
|
9
10
|
Do NOT modify agent .md files. Profile switching only updates these two JSON files.
|
|
10
11
|
</role>
|
|
@@ -48,6 +49,27 @@ Active profile: **{profile_name}**
|
|
|
48
49
|
|
|
49
50
|
<behavior>
|
|
50
51
|
|
|
52
|
+
## Step 0: Ensure GSD config read permission
|
|
53
|
+
|
|
54
|
+
Before any profile operations, ensure opencode.json has permission to read the GSD config folder:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
node ~/.config/opencode/get-shit-done/bin/gsd-oc-tools.cjs allow-read-config --dry-run
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Parse the response:
|
|
61
|
+
- **`success: true` with `action: "permission_exists"`** — Permission already configured. Continue to Step 1.
|
|
62
|
+
- **`success: true` with `action: "add_permission"`** — Permission would be added. Execute without `--dry-run`:
|
|
63
|
+
|
|
64
|
+
Attempt to switch to the saved profile:
|
|
65
|
+
```bash
|
|
66
|
+
node ~/.config/opencode/get-shit-done/bin/gsd-oc-tools.cjs allow-read-config
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- **`success: false`** — Handle error appropriately.
|
|
70
|
+
|
|
71
|
+
This ensures gsd-opencode can access workflow files, templates, and configuration from `~/.config/opencode/get-shit-done/`.
|
|
72
|
+
|
|
51
73
|
## Step 1: Load current profile
|
|
52
74
|
|
|
53
75
|
Run `get-profile` to read the current state from `.planning/oc_config.json`:
|
|
@@ -148,6 +170,8 @@ Done! Updated {profile_name} profile:
|
|
|
148
170
|
|
|
149
171
|
We just updated the `./opencode.json` file. Apply the agent settings you need to **restart your opencode**.
|
|
150
172
|
|
|
173
|
+
Note: GSD config read permission has been configured to allow access to `~/.config/opencode/get-shit-done/`.
|
|
174
|
+
|
|
151
175
|
</behavior>
|
|
152
176
|
|
|
153
177
|
<notes>
|