Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-dream-revert.md
Original file line number Diff line number Diff line change
@@ -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.
38 changes: 24 additions & 14 deletions packages/cli/src/dreamCycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface Correction {

export interface DreamState {
lastDreamAt: string;
consumedSuggestionKeys?: string[];
}

export interface DreamResult {
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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;
}
Expand Down
33 changes: 25 additions & 8 deletions packages/cli/src/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
markProcessed,
getUnprocessedCount,
runDreamCycle,
loadDreamState,
saveDreamState,
} from './dreamCycle';
import { deleteScopedRules, writeRulesForPlatforms } from './scopedRules';
Expand Down Expand Up @@ -279,6 +280,7 @@ export interface FileChangeEvent {
interface RunOnceResult {
code: ExitCodeValue;
kbContent: string;
probeRan?: boolean;
}

/**
Expand Down Expand Up @@ -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)');
}

Expand Down Expand Up @@ -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 ───────────────────────────────────
Expand Down Expand Up @@ -795,10 +803,12 @@ export async function runPipeline(ctx: RunContext): Promise<ExitCodeValue> {
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));
});
Expand Down Expand Up @@ -1002,11 +1012,13 @@ export async function runPipeline(ctx: RunContext): Promise<ExitCodeValue> {

// 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();
}
Expand Down Expand Up @@ -1035,7 +1047,11 @@ export async function runPipeline(ctx: RunContext): Promise<ExitCodeValue> {
}
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(', ')}`);
}
Expand Down Expand Up @@ -1066,7 +1082,8 @@ export async function runPipeline(ctx: RunContext): Promise<ExitCodeValue> {
}, 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;
Expand Down
Loading