Files
devclaw-gitea/lib/config/merge.ts
Lauren ten Hoor 0e24a68882 refactor: migrate role handling from tiers to roles module
- Removed the deprecated tiers.ts file and migrated all related functionality to roles/index.js.
- Updated tests and tools to reflect the new role structure, replacing references to "dev", "qa", and "architect" with "developer", "tester", and "architect".
- Adjusted workflow configurations and state management to accommodate the new role naming conventions.
- Enhanced project registration and health check tools to support dynamic role handling.
- Updated task creation, update, and completion processes to align with the new role definitions.
- Improved documentation and comments to clarify role responsibilities and usage.
2026-02-15 18:32:10 +08:00

83 lines
2.3 KiB
TypeScript

/**
* config/merge.ts — Deep merge for DevClaw config layers.
*
* Merge semantics:
* - Objects: recursively merge (sparse override)
* - Arrays: replace entirely (no merging array elements)
* - `false` for a role: marks it as disabled
* - Primitives: override
*/
import type { DevClawConfig, RoleOverride } from "./types.js";
/**
* Merge a config overlay on top of a base config.
* Returns a new config — does not mutate inputs.
*/
export function mergeConfig(
base: DevClawConfig,
overlay: DevClawConfig,
): DevClawConfig {
const merged: DevClawConfig = {};
// Merge roles
if (base.roles || overlay.roles) {
merged.roles = { ...base.roles };
if (overlay.roles) {
for (const [roleId, overrideValue] of Object.entries(overlay.roles)) {
if (overrideValue === false) {
// Disable role
merged.roles[roleId] = false;
} else if (merged.roles[roleId] === false) {
// Re-enable with override
merged.roles[roleId] = overrideValue;
} else {
// Merge role override on top of base role
const baseRole = merged.roles[roleId];
merged.roles[roleId] = mergeRoleOverride(
typeof baseRole === "object" ? baseRole : {},
overrideValue,
);
}
}
}
}
// Merge workflow
if (base.workflow || overlay.workflow) {
merged.workflow = {
initial: overlay.workflow?.initial ?? base.workflow?.initial,
states: {
...base.workflow?.states,
...overlay.workflow?.states,
},
};
// Clean up undefined initial
if (merged.workflow.initial === undefined) {
delete merged.workflow.initial;
}
}
return merged;
}
function mergeRoleOverride(
base: RoleOverride,
overlay: RoleOverride,
): RoleOverride {
return {
...base,
...overlay,
// Models: merge (don't replace)
models: base.models || overlay.models
? { ...base.models, ...overlay.models }
: undefined,
// Emoji: merge (don't replace)
emoji: base.emoji || overlay.emoji
? { ...base.emoji, ...overlay.emoji }
: undefined,
// Arrays replace entirely
...(overlay.levels ? { levels: overlay.levels } : {}),
...(overlay.completionResults ? { completionResults: overlay.completionResults } : {}),
};
}