/** * Settings declared by this domain (see `config/registry.ts`). Declaration order is the * settings-panel order; `config/all-settings.ts` registers every domain. */ import type { EditMode } from "@oh-my-pi/pi-tui/tools/edit"; import { register } from "../config/registry"; const EMPTY_STRING_RECORD: Record = {}; /** `PI_EDIT_FUZZY`: `1`/`true` force fuzzy matching, `0`/`false` disable it; `auto`/other text defers to the setting. */ function parseEditFuzzyEnv(raw: string): boolean | undefined { if (raw === "1" || raw === "true") return true; if (raw === "0" || raw === "false") return false; return undefined; } /** Every edit tool variant, as accepted by `edit.mode` and `PI_EDIT_VARIANT`. */ export const EDIT_MODES = [ "apply_patch", "hashline", "patch", "replace", "sloppy", ] as const satisfies readonly EditMode[]; /** * `edit.mode` value: an edit tool variant, or `"none"` to unregister the edit tool and force-enable * `bash` (see `resolveBuiltinToolPlan` in `tools/index.ts`). */ export type EditModeSetting = EditMode | "none"; /** Narrows free text (config values, per-model variants) to an edit mode; `undefined` when unknown. */ export function normalizeEditMode(mode?: string | null): EditMode | undefined { return EDIT_MODES.find(candidate => candidate === mode); } // ──────────────────────────────────────────────────────────────────────── // Editing // ──────────────────────────────────────────────────────────────────────── // Edit tool export const cfgEditMode = register({ id: "edit.mode", type: "enum", values: [...EDIT_MODES, "none"] as const satisfies readonly EditModeSetting[], default: "hashline", // Pins the variant: it also skips the per-model hashline fallback (see `utils/edit-mode.ts`). env: "PI_EDIT_VARIANT", ui: { tab: "files", group: "Editing", label: "Edit Mode", description: "Select the edit tool variant (replace, patch, hashline, or apply_patch); none removes the edit tool and keeps bash enabled", }, }); /** Per-model edit variant: model-selector substring (case-insensitive) → edit mode. Config-file only. */ export const cfgEditModelVariants = register({ id: "edit.modelVariants", type: "record", default: EMPTY_STRING_RECORD, }); /** {@link cfgEditModelVariants} with lowercased patterns; entries naming an unknown mode are dropped. */ export const editModelVariants = cfgEditModelVariants.map(variants => { const entries: { patternLower: string; mode: EditMode }[] = []; for (const pattern in variants) { const mode = normalizeEditMode(variants[pattern]); if (mode) entries.push({ patternLower: pattern.toLowerCase(), mode }); } return entries; }); export const cfgEditFuzzyMatch = register({ id: "edit.fuzzyMatch", type: "boolean", default: true, env: { name: "PI_EDIT_FUZZY", parse: parseEditFuzzyEnv }, ui: { tab: "files", group: "Editing", label: "Fuzzy Match", description: "Accept high-confidence fuzzy matches for whitespace differences", }, }); export const cfgEditFuzzyThreshold = register({ id: "edit.fuzzyThreshold", type: "number", default: 0.95, env: { name: "PI_EDIT_FUZZY_THRESHOLD", parse: raw => { const threshold = Number.parseFloat(raw); return threshold >= 0 && threshold <= 1 ? threshold : undefined; }, }, ui: { tab: "files", group: "Editing", label: "Fuzzy Match Threshold", description: "Similarity threshold (0-1) for accepting fuzzy matches", options: [ { value: "0.85", label: "0.85", description: "Lenient" }, { value: "0.90", label: "0.90", description: "Moderate" }, { value: "0.95", label: "0.95", description: "Default" }, { value: "0.98", label: "0.98", description: "Strict" }, ], }, }); export const cfgEditStreamingAbort = register({ id: "edit.streamingAbort", type: "boolean", default: false, ui: { tab: "files", group: "Editing", label: "Abort on Failed Preview", description: "Abort streaming edit tool calls when patch preview fails", }, }); export const cfgEditRecoverInlineEdits = register({ id: "edit.recoverInlineEdits", type: "boolean", default: true, ui: { tab: "files", group: "Editing", label: "Recover Inline Edit Payloads", description: "Execute edit payloads the model emits as plain text by converting them into edit tool calls", }, }); export const cfgEditBlockAutoGenerated = register({ id: "edit.blockAutoGenerated", type: "boolean", default: true, ui: { tab: "files", group: "Editing", label: "Block Auto-Generated Files", description: "Prevent editing of files that appear to be auto-generated (protoc, sqlc, swagger, etc.)", }, }); export const cfgEditEnforceSeenLines = register({ id: "edit.enforceSeenLines", type: "boolean", default: true, ui: { tab: "files", group: "Editing", label: "Enforce Seen-Line Guard", description: "Reject edits anchored on lines a prior read/search never displayed in full", }, }); export const cfgEditBlackboxEnabled = register({ id: "edit.blackbox.enabled", type: "boolean", default: false, ui: { tab: "files", group: "Editing", label: "Record Parse Regressions", description: "Append full before/after source when an edit introduces an AST parse failure", }, }); export const cfgEditAutoRepairEnabled = register({ id: "edit.autoRepair.enabled", type: "boolean", default: false, ui: { tab: "files", group: "Editing", label: "Auto-Repair Parse Regressions", description: "When an edit breaks a file's AST parse, ask the smol model to fix the broken region (validated by re-parse; falls back to a warning)", }, });