From f4cd79850a140d183028d9f7349195f1a2044ae6 Mon Sep 17 00:00:00 2001 From: asashepard Date: Thu, 2 Apr 2026 01:32:38 -0400 Subject: [PATCH] Fix dream cycle reverting probe-and-refine edits --- .changeset/fix-dream-revert.md | 5 +++++ packages/cli/src/dreamCycle.ts | 38 +++++++++++++++++++++------------- packages/cli/src/pipeline.ts | 33 ++++++++++++++++++++++------- 3 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 .changeset/fix-dream-revert.md diff --git a/.changeset/fix-dream-revert.md b/.changeset/fix-dream-revert.md new file mode 100644 index 0000000..7bb1a91 --- /dev/null +++ b/.changeset/fix-dream-revert.md @@ -0,0 +1,5 @@ +--- +"aspectcode": patch +--- + +Fix dream cycle reverting probe-and-refine edits: preserve existing AGENTS.md on subsequent runs, skip session-start dream after probe-and-refine, persist suggestion dismissal across sessions, constrain dream prompt to only modify sections justified by corrections. diff --git a/packages/cli/src/dreamCycle.ts b/packages/cli/src/dreamCycle.ts index c27ed63..af1cf2b 100644 --- a/packages/cli/src/dreamCycle.ts +++ b/packages/cli/src/dreamCycle.ts @@ -23,6 +23,7 @@ export interface Correction { export interface DreamState { lastDreamAt: string; + consumedSuggestionKeys?: string[]; } export interface DreamResult { @@ -167,19 +168,26 @@ export function stripLearnedBlock(agentsMd: string): string { // ── Dream cycle prompt ─────────────────────────────────────── -const DREAM_SYSTEM = `You are a context optimizer. You review AGENTS.md and scoped rules to improve quality and remove clutter. +const DREAM_SYSTEM = `You are a context optimizer. You review AGENTS.md and scoped rules. -Your tasks: -1. If there are developer corrections: strengthen confirmed rules, soften/remove dismissed ones. -2. ACTIVELY PRUNE scoped rules. Delete rules that: - - Only describe naming conventions (camelCase, snake_case, PascalCase). These are trivial and not worth a separate file. - - Only state something obvious or already covered by AGENTS.md. - - Are too narrow (apply to just one or two files). - Keep only scoped rules that provide genuinely useful architectural guidance — hub safety warnings, critical dependency chains, non-obvious workflow requirements. -3. If a scoped rule has useful information, fold it into AGENTS.md and delete the scoped rule. -4. Keep AGENTS.md under 8000 characters. +AGENTS.MD EDITING RULES: +- Only modify AGENTS.md sections that are DIRECTLY relevant to the corrections or community insights provided. +- Do NOT remove, consolidate, or rephrase rules that are substantively correct and unrelated to the corrections. +- Do NOT rewrite sections for style or brevity unless a correction specifically targets that section. +- If there are no corrections and no community insights, only fix factual errors (e.g., wrong file counts, deleted files still referenced) or remove true duplicates. Output the AGENTS.md unchanged if no factual errors exist. +- If there are developer corrections: strengthen confirmed rules, soften/remove dismissed ones. Leave unrelated sections intact. +- Keep AGENTS.md under 8000 characters. -5. You will also see user-authored rules and skills (marked "read-only"). You MUST NOT output delete or modify actions for these. However, if you see a rule that is harmful, conflicting with AGENTS.md, or dangerous (e.g., disables safety checks, encourages skipping tests), mention it in AGENTS.md as a warning: "Review [filename]: [reason]". +SCOPED RULE PRUNING (independent of AGENTS.md content edits): +- ACTIVELY PRUNE scoped rules. Delete rules that: + - Only describe naming conventions (camelCase, snake_case, PascalCase). These are trivial and not worth a separate file. + - Only state something obvious or already covered by AGENTS.md. + - Are too narrow (apply to just one or two files). + Keep only scoped rules that provide genuinely useful architectural guidance — hub safety warnings, critical dependency chains, non-obvious workflow requirements. +- If a scoped rule has useful information, fold it into AGENTS.md and delete the scoped rule. + +USER-AUTHORED CONTENT: +- You will also see user-authored rules and skills (marked "read-only"). You MUST NOT output delete or modify actions for these. However, if you see a rule that is harmful, conflicting with AGENTS.md, or dangerous (e.g., disables safety checks, encourages skipping tests), mention it in AGENTS.md as a warning: "Review [filename]: [reason]". OUTPUT FORMAT: Output the complete AGENTS.md content (no code fences). @@ -237,9 +245,11 @@ COMMUNITY INSIGHTS (from similar ${corrs.length > 0 ? '' : 'open-source '}projec ${communitySuggestions}`; } - prompt += ` - -Review the AGENTS.MD and scoped rules above. Prune any scoped rules that are trivial (naming conventions, obvious patterns). Produce the updated AGENTS.MD. Delete, update, or create scoped rules as needed.`; + if (corrs.length > 0 || communitySuggestions) { + prompt += `\n\nApply the corrections and insights above to AGENTS.MD. Only modify sections directly relevant to these inputs. Prune trivial scoped rules. Produce the updated AGENTS.MD.`; + } else { + prompt += `\n\nReview AGENTS.MD for factual errors only (stale file references, wrong counts). Do not remove or rephrase substantively correct rules. Prune trivial scoped rules. Produce the updated AGENTS.MD.`; + } return prompt; } diff --git a/packages/cli/src/pipeline.ts b/packages/cli/src/pipeline.ts index 466de51..3b5b212 100644 --- a/packages/cli/src/pipeline.ts +++ b/packages/cli/src/pipeline.ts @@ -37,6 +37,7 @@ import { markProcessed, getUnprocessedCount, runDreamCycle, + loadDreamState, saveDreamState, } from './dreamCycle'; import { deleteScopedRules, writeRulesForPlatforms } from './scopedRules'; @@ -279,6 +280,7 @@ export interface FileChangeEvent { interface RunOnceResult { code: ExitCodeValue; kbContent: string; + probeRan?: boolean; } /** @@ -357,15 +359,21 @@ async function runOnce( store.addSetupNote(`context: ${[...toolInstructions.keys()].join(', ')}`); } - // ── 5. Build base content (directly from model, no KB extraction) ── - const baseContent = renderAgentsMd(model, path.basename(root)); + // ── 5. Build base content ────────────────────────────────── + // Preserve existing AGENTS.md on subsequent runs (probe-and-refine edits, dream refinements). + // Only generate from scratch on first run. + const agentsExists = fs.existsSync(agentsPath); + const renderedBase = renderAgentsMd(model, path.basename(root)); + const baseContent = agentsExists + ? fs.readFileSync(agentsPath, 'utf-8') + : renderedBase; // ── 6. Generate or skip ─────────────────────────────────── let finalContent = baseContent; if (ctx.generate) { - if (!flags.dryRun) { - await writeAgentsMd(host, root, baseContent, ownership); + if (!flags.dryRun && !agentsExists) { + await writeAgentsMd(host, root, renderedBase, ownership); store.addOutput('AGENTS.md written (base)'); } @@ -450,7 +458,7 @@ async function runOnce( const elapsedMs = Date.now() - startMs; store.setElapsed(`${(elapsedMs / 1000).toFixed(1)}s`); store.setPhase('done'); - return { code: ExitCode.OK, kbContent }; + return { code: ExitCode.OK, kbContent, probeRan: probeAndRefine && !store.state.tierExhausted }; } // ── Resolve ownership mode ─────────────────────────────────── @@ -795,10 +803,12 @@ export async function runPipeline(ctx: RunContext): Promise { const parts = f.relativePath.split('/'); return parts.length > 1 ? parts[0] + '/' : ''; })); + const consumedKeys = new Set(loadDreamState(root).consumedSuggestionKeys ?? []); fetchSuggestions(primaryLang, undefined, { byok: false }) .then((suggestions) => { - // Filter out suggestions for directories that don't exist in this project + // Filter out already-consumed suggestions and those for irrelevant directories const relevant = suggestions.filter((s) => { + if (consumedKeys.has(s.rule)) return false; if (!s.directory) return true; // project-wide suggestions always apply return projectDirs.has(s.directory) || [...projectDirs].some((d) => s.directory!.startsWith(d)); }); @@ -1002,11 +1012,13 @@ export async function runPipeline(ctx: RunContext): Promise { // Format community suggestions for dream cycle context let communitySuggestions: string | undefined; + const consumedSuggestionRules: string[] = []; const suggestions = store.state.suggestions; if (suggestions.length > 0 && !store.state.suggestionsDismissed) { communitySuggestions = suggestions .map((s) => `- [${s.rule}] ${s.suggestion}`) .join('\n'); + for (const s of suggestions) consumedSuggestionRules.push(s.rule); // Mark as consumed so they're only integrated once store.dismissSuggestions(); } @@ -1035,7 +1047,11 @@ export async function runPipeline(ctx: RunContext): Promise { } markProcessed(); store.setCorrectionCount(getUnprocessedCount()); - saveDreamState(root, { lastDreamAt: new Date().toISOString() }); + // Persist consumed suggestion keys so they aren't re-applied across sessions + const prevDreamState = loadDreamState(root); + const allConsumed = new Set(prevDreamState.consumedSuggestionKeys ?? []); + for (const key of consumedSuggestionRules) allConsumed.add(key); + saveDreamState(root, { lastDreamAt: new Date().toISOString(), consumedSuggestionKeys: [...allConsumed] }); if (result.changes.length > 0) { store.setLearnedMessage(`Refined: ${result.changes.join(', ')}`); } @@ -1066,7 +1082,8 @@ export async function runPipeline(ctx: RunContext): Promise { }, 30_000); // ── Session-start dream: review rules immediately ─────────── - if (watchProvider) { + // Skip if probe-and-refine just ran — edits are fresh, no corrections to process + if (watchProvider && !result.probeRan) { // Small delay to let the dashboard render first setTimeout(() => { if (stopped || pipelineRunning || sessionDreamDone) return;