Files
Bohdan Triapitsyn 5f0d1623ae feat: expand magic prompts coverage and split generation prompts (#835)
- Add configurable visible/instructions prompt families for commit/PR generation, PR checks/comments flows, and git conflict resolution helpers.
- Refactor prompt sending to explicit visible + synthetic parts instead of newline-based splitting, with legacy override migration for old keys.
- Polish Magic Prompts settings UX with grouped sidebar entries, tooltip-based descriptions, AI icon, and validation that visible prompts cannot be empty across web and VS Code runtimes.
2026-04-07 22:51:14 +03:00

120 lines
3.3 KiB
JavaScript

const FILE_VERSION = 1;
const MAX_PROMPT_TEXT_LENGTH = 200_000;
const PROMPT_ID_PATTERN = /^[a-z0-9._-]{1,160}$/;
const isVisiblePromptID = (id) => typeof id === 'string' && id.endsWith('.visible');
const hasOwn = (input, key) => Object.prototype.hasOwnProperty.call(input, key);
const sanitizeOverrides = (value) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return {};
}
const next = {};
for (const [key, entry] of Object.entries(value)) {
if (!PROMPT_ID_PATTERN.test(key) || typeof entry !== 'string') {
continue;
}
next[key] = entry;
}
return next;
};
export const createMagicPromptRuntime = (dependencies) => {
const {
fsPromises,
path,
filePath,
} = dependencies;
let writeLock = Promise.resolve();
const readPromptState = async () => {
try {
const raw = await fsPromises.readFile(filePath, 'utf8');
const parsed = JSON.parse(raw);
const overrides = sanitizeOverrides(parsed?.overrides);
return {
version: FILE_VERSION,
overrides,
};
} catch (error) {
if (error && typeof error === 'object' && error.code === 'ENOENT') {
return { version: FILE_VERSION, overrides: {} };
}
console.warn('Failed to read magic prompts file:', error);
return { version: FILE_VERSION, overrides: {} };
}
};
const writePromptState = async (state) => {
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
await fsPromises.writeFile(filePath, JSON.stringify(state, null, 2), 'utf8');
};
const persist = async (mutator) => {
const run = async () => {
const current = await readPromptState();
const next = await mutator(current);
await writePromptState(next);
return next;
};
writeLock = writeLock.then(run, run);
return writeLock;
};
const setOverride = async (id, text) => {
const normalizedId = typeof id === 'string' ? id.trim() : '';
if (!PROMPT_ID_PATTERN.test(normalizedId)) {
throw new Error('Invalid prompt id');
}
if (typeof text !== 'string') {
throw new Error('Prompt text must be a string');
}
if (isVisiblePromptID(normalizedId) && text.trim().length === 0) {
throw new Error('Visible prompt text cannot be empty');
}
if (text.length > MAX_PROMPT_TEXT_LENGTH) {
throw new Error('Prompt text is too long');
}
return persist(async (state) => {
const nextOverrides = { ...state.overrides, [normalizedId]: text };
return {
version: FILE_VERSION,
overrides: nextOverrides,
};
});
};
const resetOverride = async (id) => {
const normalizedId = typeof id === 'string' ? id.trim() : '';
if (!PROMPT_ID_PATTERN.test(normalizedId)) {
throw new Error('Invalid prompt id');
}
return persist(async (state) => {
if (!hasOwn(state.overrides, normalizedId)) {
return state;
}
const nextOverrides = { ...state.overrides };
delete nextOverrides[normalizedId];
return {
version: FILE_VERSION,
overrides: nextOverrides,
};
});
};
const resetAllOverrides = async () => {
return persist(async () => ({ version: FILE_VERSION, overrides: {} }));
};
return {
readPromptState,
setOverride,
resetOverride,
resetAllOverrides,
};
};