framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
78 lines • 3.34 kB
JavaScript
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { errorMessage } from './error-message.js';
import { isHandoffLevel, HANDOFF_LEVELS } from './handoff-level.js';
/** Config file names read from the workspace root, in precedence order. */
export const FRAMEWORK_CONFIG_FILES = ['the-framework.yml', 'the-framework.yaml'];
/**
* The keys whose values come from a closed set, so parsing checks the value rather than the type
* (B5). Only the publish ladder so far; each is validated by name in {@link parseFrameworkConfig}.
*/
const ENUM_CONFIG_KEYS = ['handoff'];
/**
* The boolean-valued mode keys. This is the canonical mode list: parsing, the config-layer copy,
* resolution, and the resolved-config summary all iterate it, so a new mode is added here once and
* flows through them (only its default and any renamed output field are declared per key).
*/
export const BOOLEAN_CONFIG_KEYS = ['vanilla', 'transparent'];
/** Every config key, enum then boolean, in declaration order. */
export const CONFIG_KEYS = [...ENUM_CONFIG_KEYS, ...BOOLEAN_CONFIG_KEYS];
/**
* Read `the-framework.yml` (or `.yaml`) from a directory. A missing file yields
* `{}`. Best-effort: a malformed file is reported via `onWarn` and treated as
* empty, never a failed agent. CLI flags override whatever this returns.
*/
export async function loadFrameworkConfig(dir, onWarn) {
for (const name of FRAMEWORK_CONFIG_FILES) {
let raw;
try {
raw = await readFile(join(dir, name), 'utf8');
}
catch {
continue; // not this name; try the next
}
try {
return parseFrameworkConfig(raw, name);
}
catch (err) {
// parseFrameworkConfig already prefixes the file name in its message.
onWarn?.(`ignoring ${errorMessage(err)}`);
return {};
}
}
return {};
}
/**
* Parse and validate a `the-framework.yml` body into a {@link FrameworkFileConfig}.
* An empty document is `{}`. Throws on a non-map document or a mistyped field so
* {@link loadFrameworkConfig} can surface it as a warning.
*/
export function parseFrameworkConfig(raw, source = 'the-framework.yml') {
const data = parseYaml(raw);
if (data == null)
return {};
if (typeof data !== 'object' || Array.isArray(data)) {
throw new Error(`${source} must be a YAML map of settings`);
}
const obj = data;
const config = {};
// The one key with a closed set (B5), so it is checked by its values rather than by its type: a
// typo — or a leftover `handoff: true` — has to be an error, not a silently ignored rung that
// leaves the repo publishing more than its file says.
if (obj['handoff'] !== undefined) {
if (!isHandoffLevel(obj['handoff'])) {
throw new Error(`${source}: "handoff" must be one of ${HANDOFF_LEVELS.join(' | ')}`);
}
config.handoff = obj['handoff'];
}
for (const key of BOOLEAN_CONFIG_KEYS) {
if (obj[key] !== undefined) {
if (typeof obj[key] !== 'boolean')
throw new Error(`${source}: "${key}" must be a boolean`);
config[key] = obj[key];
}
}
return config;
}
//# sourceMappingURL=config.js.map