open-design/apps/daemon/src/critique/config.ts
marco 5dd70b5016
Some checks failed
ci / Validate workspace (push) Successful in 12m32s
landing-page-ci / Validate landing page (push) Successful in 9m41s
landing-page-deploy / Deploy landing page (push) Failing after 5m23s
github-metrics / Generate repository metrics SVG (push) Failing after 2m3s
refresh-contributors-wall / Refresh contributors wall cache bust (push) Failing after 11s
Initial import: open-design source for helix-mind.ai distribution
This repository contains the open-design daemon CLI source code, built
and packaged at https://helix-mind.ai/cli/open-design/latest.tgz for use
by the HelixMind /design slash command.

Licenses: Apache-2.0 (root) + MIT (skills/*)
2026-05-06 20:50:24 +02:00

89 lines
3.6 KiB
TypeScript

import { defaultCritiqueConfig, FALLBACK_POLICIES } from '@open-design/contracts/critique';
import type { CritiqueConfig } from '@open-design/contracts/critique';
/**
* Load CritiqueConfig from process.env. Keys map 1:1 to OD_CRITIQUE_*.
* Missing values fall back to defaultCritiqueConfig(). Invalid values
* (non-numeric, negative, out-of-range) throw RangeError so misconfig
* surfaces at boot, never silently.
*
* @see specs/current/critique-theater.md § Configuration (env vars)
*/
export function loadCritiqueConfigFromEnv(env: NodeJS.ProcessEnv = process.env): CritiqueConfig {
const defaults = defaultCritiqueConfig();
const enabled = parseEnabled(env['OD_CRITIQUE_ENABLED'], defaults.enabled);
const maxRounds = parsePositiveInt('OD_CRITIQUE_MAX_ROUNDS', env['OD_CRITIQUE_MAX_ROUNDS'], defaults.maxRounds);
const scoreThreshold = parseNonNegativeFloat('OD_CRITIQUE_SCORE_THRESHOLD', env['OD_CRITIQUE_SCORE_THRESHOLD'], defaults.scoreThreshold);
const scoreScale = parsePositiveInt('OD_CRITIQUE_SCORE_SCALE', env['OD_CRITIQUE_SCORE_SCALE'], defaults.scoreScale);
const perRoundTimeoutMs = parsePositiveInt('OD_CRITIQUE_PER_ROUND_TIMEOUT_MS', env['OD_CRITIQUE_PER_ROUND_TIMEOUT_MS'], defaults.perRoundTimeoutMs);
const totalTimeoutMs = parsePositiveInt('OD_CRITIQUE_TOTAL_TIMEOUT_MS', env['OD_CRITIQUE_TOTAL_TIMEOUT_MS'], defaults.totalTimeoutMs);
const parserMaxBlockBytes = parsePositiveInt('OD_CRITIQUE_PARSER_MAX_BLOCK_BYTES', env['OD_CRITIQUE_PARSER_MAX_BLOCK_BYTES'], defaults.parserMaxBlockBytes);
const fallbackPolicy = parseFallbackPolicy(env['OD_CRITIQUE_FALLBACK_POLICY'], defaults.fallbackPolicy);
// Cross-field validation: threshold cannot exceed scale.
if (scoreThreshold > scoreScale + 1e-9) {
throw new RangeError(
`OD_CRITIQUE_SCORE_THRESHOLD (${scoreThreshold}) must be <= OD_CRITIQUE_SCORE_SCALE (${scoreScale})`,
);
}
return {
...defaults,
enabled,
maxRounds,
scoreThreshold,
scoreScale,
perRoundTimeoutMs,
totalTimeoutMs,
parserMaxBlockBytes,
fallbackPolicy,
};
}
// ---------------------------------------------------------------------------
// Parsing helpers
// ---------------------------------------------------------------------------
function parseEnabled(raw: string | undefined, fallback: boolean): boolean {
if (raw === undefined) return fallback;
const v = raw.trim().toLowerCase();
return v === 'true' || v === '1' || v === 'yes';
}
function parsePositiveInt(key: string, raw: string | undefined, fallback: number): number {
if (raw === undefined) return fallback;
const n = Number(raw);
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
throw new RangeError(
`${key} must be a positive integer, got "${raw}"`,
);
}
return n;
}
function parseNonNegativeFloat(key: string, raw: string | undefined, fallback: number): number {
if (raw === undefined) return fallback;
const n = Number(raw);
if (!Number.isFinite(n) || n < 0) {
throw new RangeError(
`${key} must be a non-negative finite number, got "${raw}"`,
);
}
return n;
}
function parseFallbackPolicy(
raw: string | undefined,
fallback: CritiqueConfig['fallbackPolicy'],
): CritiqueConfig['fallbackPolicy'] {
if (raw === undefined) return fallback;
const trimmed = raw.trim();
if (FALLBACK_POLICIES.includes(trimmed as CritiqueConfig['fallbackPolicy'])) {
return trimmed as CritiqueConfig['fallbackPolicy'];
}
throw new RangeError(
`OD_CRITIQUE_FALLBACK_POLICY must be one of ${FALLBACK_POLICIES.join(', ')}, got "${raw}"`,
);
}