201 lines
5.3 KiB
JavaScript
201 lines
5.3 KiB
JavaScript
/**
|
|
* oc-config.cjs — Profile configuration operations for gsd-oc-tools CLI
|
|
*
|
|
* Provides functions for loading profile config and applying model assignments to opencode.json.
|
|
* Follows gsd-tools.cjs architecture pattern.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
/**
|
|
* Valid profile types whitelist
|
|
*/
|
|
const VALID_PROFILES = ['simple', 'smart', 'genius'];
|
|
|
|
/**
|
|
* Profile to agent mapping
|
|
* Maps profile keys to opencode.json agent names
|
|
*/
|
|
const PROFILE_AGENT_MAPPING = {
|
|
// Planning agents
|
|
planning: [
|
|
'gsd-planner',
|
|
'gsd-plan-checker',
|
|
'gsd-phase-researcher',
|
|
'gsd-roadmapper',
|
|
'gsd-project-researcher',
|
|
'gsd-research-synthesizer',
|
|
'gsd-codebase-mapper'
|
|
],
|
|
// Execution agents
|
|
execution: [
|
|
'gsd-executor',
|
|
'gsd-debugger'
|
|
],
|
|
// Verification agents
|
|
verification: [
|
|
'gsd-verifier',
|
|
'gsd-integration-checker'
|
|
]
|
|
};
|
|
|
|
/**
|
|
* Load profile configuration from .planning/config.json
|
|
*
|
|
* @param {string} cwd - Current working directory
|
|
* @returns {Object|null} Parsed config object or null on error
|
|
*/
|
|
function loadProfileConfig(cwd) {
|
|
try {
|
|
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
|
|
if (!fs.existsSync(configPath)) {
|
|
return null;
|
|
}
|
|
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
const config = JSON.parse(content);
|
|
|
|
return config;
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply profile configuration to opencode.json
|
|
* Updates agent model assignments based on profile
|
|
*
|
|
* @param {string} opencodePath - Path to opencode.json
|
|
* @param {string} configPath - Path to .planning/config.json
|
|
* @returns {Object} {success: true, updated: [agentNames]} or {success: false, error: {code, message}}
|
|
*/
|
|
function applyProfileToOpencode(opencodePath, configPath) {
|
|
try {
|
|
// Load profile config
|
|
let config;
|
|
if (fs.existsSync(configPath)) {
|
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
config = JSON.parse(content);
|
|
} else {
|
|
return {
|
|
success: false,
|
|
error: {
|
|
code: 'CONFIG_NOT_FOUND',
|
|
message: `.planning/config.json not found at ${configPath}`
|
|
}
|
|
};
|
|
}
|
|
|
|
// Validate profile_type
|
|
const profileType = config.profile_type || config.profiles?.profile_type;
|
|
if (!profileType) {
|
|
return {
|
|
success: false,
|
|
error: {
|
|
code: 'PROFILE_NOT_FOUND',
|
|
message: 'profile_type not found in config.json'
|
|
}
|
|
};
|
|
}
|
|
|
|
if (!VALID_PROFILES.includes(profileType)) {
|
|
return {
|
|
success: false,
|
|
error: {
|
|
code: 'INVALID_PROFILE',
|
|
message: `Invalid profile_type: "${profileType}". Valid profiles: ${VALID_PROFILES.join(', ')}`
|
|
}
|
|
};
|
|
}
|
|
|
|
// Load opencode.json
|
|
if (!fs.existsSync(opencodePath)) {
|
|
return {
|
|
success: false,
|
|
error: {
|
|
code: 'CONFIG_NOT_FOUND',
|
|
message: `opencode.json not found at ${opencodePath}`
|
|
}
|
|
};
|
|
}
|
|
|
|
const opencodeContent = fs.readFileSync(opencodePath, 'utf8');
|
|
const opencodeData = JSON.parse(opencodeContent);
|
|
|
|
// Get model assignments from profile
|
|
// Support both structures: profiles.planning or profiles.models.planning
|
|
const profiles = config.profiles || {};
|
|
let profileModels;
|
|
|
|
// Try new structure first: profiles.models.{planning|execution|verification}
|
|
if (profiles.models && typeof profiles.models === 'object') {
|
|
profileModels = profiles.models;
|
|
} else {
|
|
// Fallback to old structure: profiles.{planning|execution|verification}
|
|
profileModels = profiles[profileType] || {};
|
|
}
|
|
|
|
if (!profileModels.planning && !profileModels.execution && !profileModels.verification) {
|
|
return {
|
|
success: false,
|
|
error: {
|
|
code: 'PROFILE_NOT_FOUND',
|
|
message: `No model assignments found for profile "${profileType}"`
|
|
}
|
|
};
|
|
}
|
|
|
|
// Apply model assignments to agents
|
|
const updatedAgents = [];
|
|
|
|
// Initialize agent object if it doesn't exist
|
|
if (!opencodeData.agent) {
|
|
opencodeData.agent = {};
|
|
}
|
|
|
|
// Apply each profile category
|
|
for (const [category, agentNames] of Object.entries(PROFILE_AGENT_MAPPING)) {
|
|
const modelId = profileModels[category];
|
|
|
|
if (modelId) {
|
|
for (const agentName of agentNames) {
|
|
// Handle both string and object agent configurations
|
|
if (typeof opencodeData.agent[agentName] === 'string') {
|
|
opencodeData.agent[agentName] = modelId;
|
|
} else if (typeof opencodeData.agent[agentName] === 'object' && opencodeData.agent[agentName] !== null) {
|
|
opencodeData.agent[agentName].model = modelId;
|
|
} else {
|
|
opencodeData.agent[agentName] = modelId;
|
|
}
|
|
updatedAgents.push(agentName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// write updated opencode.json
|
|
fs.writeFileSync(opencodePath, JSON.stringify(opencodeData, null, 2) + '\n', 'utf8');
|
|
|
|
return {
|
|
success: true,
|
|
updated: updatedAgents
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
success: false,
|
|
error: {
|
|
code: 'UPDATE_FAILED',
|
|
message: `Failed to apply profile: ${err.message}`
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
loadProfileConfig,
|
|
applyProfileToOpencode,
|
|
VALID_PROFILES,
|
|
PROFILE_AGENT_MAPPING
|
|
};
|