Files
brain/tools/brain-config.mjs
T
2026-06-16 04:24:46 +03:00

79 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* brain-config — единый источник проектной настройки мозга (.claude/brain.local.md).
*
* Чистый парсер frontmatter (parseBrainConfig) + resolveConfig с fail-safe
* дефолтами (спека 2026-06-15-brain-config-module-spec.md §D3) + I/O-обёртка
* loadConfig. Захардкоженные «знания про конкретный проект» из tools/*.mjs
* получают один источник правды; направления отказа безопасны (отсутствие ключа
* не отключает молча безопасность/деньги/защиту).
*/
import fsDefault from 'node:fs';
const FM_RE = /^---\n([\s\S]*?)\n---/;
/** Минимальный YAML-frontmatter: `key: value` + список ` - item`. Без зависимостей. Чистая. */
export function parseBrainConfig(md) {
const m = FM_RE.exec(String(md || ''));
if (!m) return {};
const out = {};
let curKey = null;
for (const line of m[1].split('\n')) {
const item = /^\s+-\s+(.*)$/.exec(line);
if (item && curKey) {
(out[curKey] ||= []).push(item[1].trim());
continue;
}
const kv = /^([A-Za-z_]+):\s*(.*)$/.exec(line);
if (!kv) continue;
const [, k, v] = kv;
if (v === '') {
out[k] = [];
curKey = k;
} else {
out[k] = /^\d+$/.test(v) ? Number(v) : v;
curKey = null;
}
}
return out;
}
/** Безопасные дефолты (§D3). Отсутствие ключа не отключает молча защиту/деньги. */
const DEFAULTS = Object.freeze({
state_dir: '.claude/brain-state',
evidence_archive: 'brain-state',
normative_files: [],
registry_path: '',
project_url_whitelist: [],
classifier_context: 'generic project (no profile configured)',
economy_default: '100',
protected_paths: [],
tool_registry_path: 'docs/Tooling_v8_3.md',
});
/** Наложить дефолты + fail-safe направления (§D3). Чистая. */
export function resolveConfig(raw) {
const c = { ...DEFAULTS, ...(raw || {}) };
// project_url_whitelist: пусто/не-массив → fail-CLOSED (внешка закрыта), не «пускать всё».
c.project_url_whitelist_failClosed = !(Array.isArray(c.project_url_whitelist) && c.project_url_whitelist.length > 0);
return c;
}
/** I/O-обёртка: прочитать .claude/brain.local.md проекта (нет файла → дефолты). */
export function loadConfig(root = '.', fsImpl = fsDefault) {
let md = '';
try {
md = fsImpl.readFileSync(`${root}/.claude/brain.local.md`, 'utf8');
} catch {
md = '';
}
return resolveConfig(parseBrainConfig(md));
}
/** fail-safe резолвер state_dir (§5.1): непустая строка → как есть; иначе → безопасный дефолт
* .claude/brain-state + warnedFallback (НЕ тихий no-op — wiring издаёт warn и пишет в fallback). */
export function resolveStateDir(value) {
const v = typeof value === 'string' ? value.trim() : '';
if (v.length > 0) return { stateDir: v, warnedFallback: false };
return { stateDir: '.claude/brain-state', warnedFallback: true };
}