/** * Layered `team-mode` configuration — the PURE layer (package ①, 2026-10-06). * * `team-mode.jsonc` is the ONLY configuration source. There is no `TM_*` * config env var any more: the registry below carries each key's default and * its one-line doc, and the template generator (`config-template.ts`) renders * the global file straight from it, so a comment and a key can never drift. * * Two file layers only, per the user's decision (2026-10-06): the host's own * config has NO session layer (opencode.ai/v2/docs/config), so neither do we. * * global < project * * Within the project layer the `.opencode/` file wins over the direct one, * mirroring the host's own discovery order (direct files merged * farthest→closest, then `.opencode/` dirs in the same order — every * discovered `.opencode` config overrides every direct config). * * This module is deliberately IO-free: it takes raw JSONC text (or null when * a file does not exist) and returns a resolved view. Reading the files, * the scope gate and the trajectory line are package ②'s job. * * Three rules the design pins, each implemented here: * - a layer that fails to PARSE is skipped WHOLE and reported — never a * half-applied config (a syntax error in a project file must not take the * global layer down with it, and must not apply "the keys that happened to * parse"); * - a single key with the WRONG TYPE is dropped from that layer only, and * the next layer down answers for it; * - a RED-LINE key is honored ONLY in the GLOBAL file (D1). Written in a * project file it is IGNORED outright and reported (see RED_LINE_EXEMPT_KEYS). */ /** Env-like record — kept local so this module has no import cycle with config.ts. */ export type EnvLike = Record; export type ConfigValueType = "number" | "string" | "string[]" | "record"; /** One honored knob: its canonical file key, the JSON type a file value must * have, its default, and the one-line doc the template renders. The registry * IS the "known key" set — a key absent here is reported as unknown (warned, * never applied). */ export interface ConfigKeySpec { /** Canonical file key (camelCase; matches the TmConfig field). */ key: string; type: ConfigValueType; /** The default value — the ONLY default source. `resolveConfig` fills it. */ default: unknown; /** One-line Chinese doc — the template comment's only source. */ doc: string; /** Red-line: honored ONLY in the global file; a project value is ignored. */ redLine?: boolean; /** Why a project file may not override it (shown in the ignored-key report). */ redLineReason?: string; /** Clamp floor for a `number` value. */ min?: number; /** Clamp ceiling for a `number` value. */ max?: number; /** Legal values for a `string` (e.g. on/off). */ enum?: readonly string[]; } /** Default tm_bash read-only allowlist (P3, command-level). The PS * -Object entries are pure pipeline formatters — no write capability. */ export declare const DEFAULT_BASH_READONLY_ALLOWED: readonly string[]; /** * Seeded tm_webfetch / tm_search domain allowlist — every host the search * engines and data sources ride (all reachable from mainland China without * API keys): wiki term / bilibili search / bing CN + international / baidu / * sogou / 360, the npm registry (JSON search + package metadata) and the * GitHub (search API + repo pages + raw/gist content), its ghproxy.net * mainland mirror, and PARENT domains for baidu/moegirl so every sibling * subdomain (baike./tieba./mzh./mobile.) is covered. A site's OWN asset CDN * on a brand-unrelated domain has to be seeded too — same-site cannot infer it, * and blocking it is what makes a page render blank (bdimg.com below is that * case, measured). Subdomains of an entry are included; a lone "*" opens * every host. */ export declare const DEFAULT_WEBFETCH_DOMAINS: readonly string[]; /** * The honored-key registry — the single source of truth for keys, types, * defaults and docs. Extend it when a new knob is wired: an unlisted real * knob would be reported as "unknown" (a false positive), and a key missing * here is missing from the generated template too. * * `default` is the ONLY default source; `resolveConfig` (src/tm/config.ts) * fills it and clamps `number` values to `min`/`max`. A `number` whose * default is an integer is truncated to an integer — UNLESS its `max` is below * 1, because truncating a sub-unit knob (retryJitter) would always discard the * value. (`searchRelevanceFloor` is a fraction too, but its max is 1, not below * it — it survives truncation via its non-integer default, not via this * exemption.) */ export declare const CONFIG_KEYS: readonly ConfigKeySpec[]; /** The red-line keys, derived from the registry (single source of truth). */ export declare const RED_LINE_EXEMPT_KEYS: readonly string[]; /** Standard layer names, low→high. */ export type LayerSource = "global" | "project" | "project:.opencode"; export interface SkippedLayer { name: string; reason: string; } export interface IgnoredRedLineKey { key: string; layer: string; value: unknown; } export interface UnknownKey { key: string; layer: string; } export interface LayeredConfigResult { /** Final merged value per canonical key, from the FILE layers only. A value * is the parsed JSON value. Keys absent everywhere are not listed (the * caller applies the registry defaults). */ values: Record; /** For every key present in a file layer: which layer it came from. */ perKeySource: Record; /** Layers dropped whole because they failed to parse. */ skippedLayers: SkippedLayer[]; /** Red-line keys a PROJECT file tried to set (ignored; global/default answers). */ ignoredRedLineKeys: IgnoredRedLineKey[]; /** Keys not in the registry (warned, never applied). */ unknownKeys: UnknownKey[]; /** True when the caller removed every file layer (test injection). */ envOnlyActive: boolean; } export interface ResolveLayeredConfigInput { /** Global file raw JSONC, or null/undefined when it does not exist. */ globalLayer?: string | null; /** Project file(s). A single raw text, or an array ordered low→high * (`[direct, .opencode]`) so the `.opencode/` file wins. */ projectLayer?: string | null | Array; /** Override the red-line exempt set (defaults to RED_LINE_EXEMPT_KEYS). */ exempt?: readonly string[]; /** Remove every file layer (test injection). */ envOnly?: boolean; } /** * Mask `//` and block comments to spaces at IDENTICAL offsets, so a * commented-out key can never be mistaken for a live one. String contents * (including a `//` inside a string) are preserved byte-for-byte. Ported * from scripts/lib/config-surgery.cjs:19 — deliberately re-implemented here * rather than imported, so the pure layer carries no CJS dependency. */ export declare function maskJsoncComments(src: string): string; /** Parse JSONC (comments + trailing commas) into a value. Throws on a real * syntax error — the caller turns that into a whole-layer skip. */ export declare function parseJsonc(src: string): unknown; export declare function isPlainObject(v: unknown): v is Record; /** * Resolve the layered config. Pure: no IO, no globals. See the module * header for the three rules. */ export declare function resolveLayeredConfig(input: ResolveLayeredConfigInput): LayeredConfigResult; //# sourceMappingURL=config-layers.d.ts.map