diff --git a/src/settings.ts b/src/settings.ts index ae7fb4a..bb2a71d 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,5 +1,19 @@ +import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; +import { SettingsManager, getAgentDir } from "@earendil-works/pi-coding-agent"; import type { AutoCompactIntegration } from "./state"; +/** JSON key in global/project settings.json (SPEC §7.3). */ +export const SKILL_REINJECT_SETTINGS_KEY = "skillReinject"; + +export type PartialSkillReinjectSettings = Partial; + +const AUTO_COMPACT_INTEGRATION_VALUES: readonly AutoCompactIntegration[] = [ + "auto", + "defer", + "immediate", + "off", +]; + /** Global/project skillReinject.* settings (SPEC §7.3). */ export interface SkillReinjectSettings { enabled: boolean; @@ -23,3 +37,67 @@ export const DEFAULT_SKILL_REINJECT_SETTINGS: Readonly = export function createDefaultSettings(): SkillReinjectSettings { return { ...DEFAULT_SKILL_REINJECT_SETTINGS }; } + +function isAutoCompactIntegration(value: unknown): value is AutoCompactIntegration { + return ( + typeof value === "string" && + (AUTO_COMPACT_INTEGRATION_VALUES as readonly string[]).includes(value) + ); +} + +/** Parse unknown JSON; invalid fields are ignored. */ +export function parseSkillReinjectPartial(raw: unknown): PartialSkillReinjectSettings { + if (!raw || typeof raw !== "object" || Array.isArray(raw)) { + return {}; + } + const obj = raw as Record; + const result: PartialSkillReinjectSettings = {}; + if (typeof obj.enabled === "boolean") { + result.enabled = obj.enabled; + } + if (typeof obj.trackReadPaths === "boolean") { + result.trackReadPaths = obj.trackReadPaths; + } + if (typeof obj.triggerTurn === "boolean") { + result.triggerTurn = obj.triggerTurn; + } + if (typeof obj.reinjectOnManualCompaction === "boolean") { + result.reinjectOnManualCompaction = obj.reinjectOnManualCompaction; + } + if (isAutoCompactIntegration(obj.autoCompactIntegration)) { + result.autoCompactIntegration = obj.autoCompactIntegration; + } + if (typeof obj.suffix === "string") { + result.suffix = obj.suffix; + } + return result; +} + +/** Project overrides global; both layers merge onto defaults (SPEC §7.3). */ +export function mergeSkillReinjectSettings( + global: PartialSkillReinjectSettings, + project: PartialSkillReinjectSettings, +): SkillReinjectSettings { + return { + ...createDefaultSettings(), + ...global, + ...project, + }; +} + +function extractSkillReinject(settings: object): PartialSkillReinjectSettings { + return parseSkillReinjectPartial( + (settings as Record)[SKILL_REINJECT_SETTINGS_KEY], + ); +} + +/** Merged global + project settings via Pi SettingsManager (SPEC §7.3). */ +export function readSettings(ctx: ExtensionContext): SkillReinjectSettings { + const manager = SettingsManager.create(ctx.cwd, getAgentDir(), { + projectTrusted: ctx.isProjectTrusted(), + }); + return mergeSkillReinjectSettings( + extractSkillReinject(manager.getGlobalSettings()), + extractSkillReinject(manager.getProjectSettings()), + ); +}