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.
This commit is contained in:
committed by
GitHub
parent
2e5b02e753
commit
5f0d1623ae
@@ -0,0 +1,119 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user