Autoapply edits if env variable configured

This commit is contained in:
khalid@traclabs.com
2026-04-23 07:58:35 -05:00
parent fdf6124fa1
commit 233fb6d003
4 changed files with 75 additions and 14 deletions

View File

@@ -34,6 +34,8 @@ LOG_LEVEL=debug
# Proposals
PROPOSAL_TTL_MS=900000
# Set to "true" to skip the YES/NO confirmation step and apply edits immediately
AUTO_APPLY_EDITS=false
# Editor auth
EDITOR_SESSION_SECRET=change-me-to-another-random-string

View File

@@ -242,31 +242,68 @@ export async function generateSummary(params: {
after: unknown;
repoRelativePath: string;
userMessage: string;
autoApply?: boolean;
chat?: LlmChatCaller;
}): Promise<string> {
const chat = params.chat || ollamaChat;
const messages = [
{
role: 'system',
content: `You write short, friendly summaries of website changes for a small business owner who manages their site via text message.
const proposalPrompt = `You write short, friendly summaries of PROPOSED website changes for a small business owner who manages their site via text message.
IMPORTANT: The change has NOT been applied yet. This is a proposal the owner must approve. Your summary will be shown to the owner prefixed with "I'd like to: ", so write it as a proposed action — what WILL happen if they approve.
Rules:
- Keep it under 140 characters.
- Describe what visitors will SEE on the website, not what changed in the data.
- Use plain, everyday language. No technical terms, no field names, no file paths, no JSON jargon.
- Write in a warm, conversational tone — like texting a friend.
- Write as a proposed future action — use phrasing like "show…", "add…", "update…", "hide…".
- NEVER use past tense or phrases like "is now live", "has been updated", "done", "all set".
Good examples:
- "Show the promo banner on your site"
- "Update your main headline to 'Welcome Home'"
- "Hide the testimonials section from visitors"
- "Add a new event: Wine Tasting on June 15"
- "Change the About section text"
- "show the promo banner on your site"
- "update your main headline to 'Welcome Home'"
- "hide the testimonials section from visitors"
- "add a new event: Wine Tasting on June 15"
- "change the About section text"
Bad examples (sounds already done — never do this):
- "The promo banner is now live on your site!"
- "Your headline has been updated!"
- "You got it! The sale banner is showing"
- "Done! Testimonials are hidden"
Bad examples (too technical — never do this):
- "Change promo-banner visible from false to true"
- "Update hero.headline field"
- "Modify content/sections/about.json"`,
- "Modify content/sections/about.json"`;
const autoApplyPrompt = `You write short, friendly summaries of website changes for a small business owner who manages their site via text message.
IMPORTANT: The change has ALREADY been applied. Your summary will be shown prefixed with "Done! I went ahead and ", so write it as a completed past-tense action.
Rules:
- Keep it under 140 characters.
- Describe what visitors will NOW SEE on the website, not what changed in the data.
- Use plain, everyday language. No technical terms, no field names, no file paths, no JSON jargon.
- Write in past tense — use phrasing like "updated…", "added…", "showed…", "hid…".
- Start lowercase (the sentence prefix is already provided).
- NEVER use future tense or proposal phrasing like "I'd like to", "will show", "would add".
Good examples:
- "showed the promo banner on your site"
- "updated your main headline to 'Welcome Home'"
- "hid the testimonials section from visitors"
- "added a new event: Wine Tasting on June 15"
- "changed the About section text"
Bad examples (too technical — never do this):
- "Change promo-banner visible from false to true"
- "Update hero.headline field"
- "Modify content/sections/about.json"`;
const messages = [
{
role: 'system',
content: params.autoApply ? autoApplyPrompt : proposalPrompt,
},
{
role: 'user',

View File

@@ -12,6 +12,7 @@ import { SMS_TEMPLATES } from '../sms/templates.js';
import { logger } from '../logger.js';
const REPO_ROOT = process.env.REPO_ROOT || '.';
const AUTO_APPLY = process.env.AUTO_APPLY_EDITS === 'true';
/** Get a friendly display name for a manifest entry. */
function sectionDisplayName(m: ManifestEntry): string {
@@ -136,6 +137,7 @@ async function handlePropose(job: Extract<EditJobPayload, { kind: 'propose' }>)
after: editedJson,
repoRelativePath,
userMessage: job.message,
autoApply: AUTO_APPLY,
});
// Step 5: Store proposal
@@ -154,9 +156,26 @@ async function handlePropose(job: Extract<EditJobPayload, { kind: 'propose' }>)
log.info({ event: 'proposal.created', proposalId, path: repoRelativePath }, 'Proposal created');
// Step 6: Notify user
if (job.smsReplyMeta) {
await sendSms(job.smsReplyMeta.from, job.smsReplyMeta.to, SMS_TEMPLATES.PROPOSAL_SUMMARY(summary, proposalId));
// Step 6: Auto-apply or ask for confirmation
if (AUTO_APPLY) {
const validation = schema.safeParse(editedJson);
if (!validation.success) {
log.error({ event: 'auto_apply.validation_failed', errors: validation.error.message }, 'Auto-apply validation failed');
updateProposalStatus(proposalId, 'rejected');
return;
}
writeContentFile(repoRelativePath, validation.data, { proposalId, source: job.source });
updateProposalStatus(proposalId, 'applied');
log.info({ event: 'proposal.auto_applied', proposalId, path: repoRelativePath }, 'Proposal auto-applied');
if (job.smsReplyMeta) {
await sendSms(job.smsReplyMeta.from, job.smsReplyMeta.to, SMS_TEMPLATES.AUTO_APPLIED(summary));
}
} else {
if (job.smsReplyMeta) {
await sendSms(job.smsReplyMeta.from, job.smsReplyMeta.to, SMS_TEMPLATES.PROPOSAL_SUMMARY(summary, proposalId));
}
}
} catch (err) {

View File

@@ -5,6 +5,9 @@ export const SMS_TEMPLATES = {
APPLIED: (summary: string) =>
`Done! ${summary} Your site will update in a moment.`,
AUTO_APPLIED: (summary: string) =>
`Done! I went ahead and ${summary} Your site will update in a moment.`,
REJECTED: () =>
`No problem — I cancelled that change. Just text me whenever you'd like to make an edit!`,