Phase 2: add settings read — merge global and project via SettingsManager.

Parses skillReinject from Pi settings layers with defaults and project-trust
aware loading so extension code shares one merged settings view.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 10:11:43 +07:00
parent 04dcab5b42
commit d8abd46f90
+78
View File
@@ -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"; 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<SkillReinjectSettings>;
const AUTO_COMPACT_INTEGRATION_VALUES: readonly AutoCompactIntegration[] = [
"auto",
"defer",
"immediate",
"off",
];
/** Global/project skillReinject.* settings (SPEC §7.3). */ /** Global/project skillReinject.* settings (SPEC §7.3). */
export interface SkillReinjectSettings { export interface SkillReinjectSettings {
enabled: boolean; enabled: boolean;
@@ -23,3 +37,67 @@ export const DEFAULT_SKILL_REINJECT_SETTINGS: Readonly<SkillReinjectSettings> =
export function createDefaultSettings(): SkillReinjectSettings { export function createDefaultSettings(): SkillReinjectSettings {
return { ...DEFAULT_SKILL_REINJECT_SETTINGS }; 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<string, unknown>;
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<string, unknown>)[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()),
);
}