← Files ForgeMindARCHIVED FILE

src/config.mjs

2.47 KB · Oct 2, 2026 · 00:31 UTC

↓ Download file

import { readFile } from 'node:fs/promises';
import path from 'node:path';

import { ForgeMindError } from './errors.mjs';
import { DEFAULT_POLICY, mergePolicies } from './policy.mjs';

export async function loadConfig(workspace, options = {}) {
  const shared = await readConfig(path.join(workspace, 'forgemind.config.json'));
  const personal = await readConfig(path.join(workspace, '.forgemind.local.json'));
  const merged = mergePolicies(DEFAULT_POLICY, shared?.policy, personal?.policy, {
    approvedWeakening: Boolean(options.approvedWeakening),
  });
  return {
    schemaVersion: 1,
    policy: merged.policy,
    policyRejections: merged.rejections,
    redaction: {
      allowlist: [...new Set([...(shared?.redaction?.allowlist ?? []), ...(personal?.redaction?.allowlist ?? [])])],
    },
    routing: {
      maxSkillTokens: Math.min(shared?.routing?.maxSkillTokens ?? 800, personal?.routing?.maxSkillTokens ?? 800),
    },
    autopilot: {
      adapters: [...(shared?.autopilot?.adapters ?? []), ...(personal?.autopilot?.adapters ?? [])],
      connectors: [...(shared?.autopilot?.connectors ?? []), ...(personal?.autopilot?.connectors ?? [])],
    },
    sources: {
      shared: shared ? 'forgemind.config.json' : null,
      personal: personal ? '.forgemind.local.json' : null,
    },
  };
}

async function readConfig(candidate) {
  let content;
  try {
    content = await readFile(candidate, 'utf8');
  } catch (error) {
    if (error.code === 'ENOENT') return null;
    throw error;
  }
  let config;
  try {
    config = JSON.parse(content);
  } catch (error) {
    throw new ForgeMindError('FM_CONFIG_INVALID', `${path.basename(candidate)} is not valid JSON: ${error.message}`);
  }
  if (config.schemaVersion !== 1) {
    throw new ForgeMindError('FM_CONFIG_INVALID', `${path.basename(candidate)} must use schemaVersion 1`);
  }
  const unsupported = Object.keys(config).filter((key) => !['schemaVersion', 'policy', 'redaction', 'routing', 'autopilot'].includes(key));
  if (unsupported.length) {
    throw new ForgeMindError('FM_CONFIG_INVALID', `${path.basename(candidate)} has unsupported fields: ${unsupported.join(', ')}`);
  }
  if (config.routing?.maxSkillTokens !== undefined && (!Number.isInteger(config.routing.maxSkillTokens) || config.routing.maxSkillTokens < 100)) {
    throw new ForgeMindError('FM_CONFIG_INVALID', `${path.basename(candidate)} routing.maxSkillTokens must be an integer of at least 100`);
  }
  return config;
}

SHA-256: db55f542895c394e3ca82cad85a1217fc5eed15e0600a7f13b73fa02988072d7