diff --git a/src/cli/init.ts b/src/cli/init.ts index 8d862e6..0ccc470 100644 --- a/src/cli/init.ts +++ b/src/cli/init.ts @@ -44,6 +44,8 @@ const CREATE_IF_MISSING = [ "memory.md", "anatomy.md", "STATUS.md", + "TODO.md", + "ROADMAP.md", "token-ledger.json", "buglog.json", "cron-manifest.json", @@ -206,6 +208,16 @@ export async function initCommand(options?: { agent?: string[] }): Promise seedStatus(wolfDir, projectRoot); } + // --- TODO.md: substitute {{PROJECT_NAME}} / {{DATE}} when freshly created --- + if (newlyCreated.has("TODO.md")) { + seedTemplateVars(path.join(wolfDir, "TODO.md"), projectRoot); + } + + // --- ROADMAP.md: substitute {{PROJECT_NAME}} / {{DATE}} when freshly created --- + if (newlyCreated.has("ROADMAP.md")) { + seedTemplateVars(path.join(wolfDir, "ROADMAP.md"), projectRoot); + } + // --- Token ledger: set created_at only if empty --- const ledgerPath = path.join(wolfDir, "token-ledger.json"); const ledger = readJSON>(ledgerPath, {}); @@ -467,7 +479,7 @@ function readTemplateContent(filename: string, templatesDir: string): string { function getEmbeddedTemplate(filename: string): string { const templates: Record = { "claude-md-snippet.md": `# OpenWolf\n\n@.wolf/OPENWOLF.md\n\nThis project uses OpenWolf for context management. Read and follow .wolf/OPENWOLF.md every session. Check .wolf/cerebrum.md before generating code. Check .wolf/anatomy.md before reading files.`, - "claude-rules-openwolf.md": `---\ndescription: OpenWolf protocol enforcement β€” active on all files\nglobs: **/*\n---\n\n- Read .wolf/STATUS.md FIRST when resuming a session β€” it contains current quest, next steps, decisions\n- Update .wolf/STATUS.md (βœ… done / πŸš€ next quest) when a quest finishes or before suggesting /clear\n- Check .wolf/anatomy.md before reading any project file\n- Check .wolf/cerebrum.md Do-Not-Repeat list before generating code\n- After writing or editing files, update .wolf/anatomy.md and append to .wolf/memory.md\n- After receiving a user correction, update .wolf/cerebrum.md immediately (Preferences, Learnings, or Do-Not-Repeat)\n- LEARN from every interaction: if you discover a convention, user preference, or project pattern, add it to .wolf/cerebrum.md. Low threshold β€” when in doubt, log it.\n- BEFORE fixing any bug or error: read .wolf/buglog.json for known fixes\n- AFTER fixing any bug, error, failed test, failed build, or user-reported problem: ALWAYS log to .wolf/buglog.json with error_message, root_cause, fix, and tags\n- If you edit a file more than twice in a session, that likely indicates a bug β€” log it to .wolf/buglog.json\n- When the user asks to change/pick/migrate UI framework: read .wolf/reframe-frameworks.md, ask decision questions, recommend a framework, then execute with the framework's prompt`, + "claude-rules-openwolf.md": `---\ndescription: OpenWolf protocol enforcement β€” active on all files\nglobs: **/*\n---\n\n- Read .wolf/STATUS.md FIRST when resuming a session β€” it contains current quest, next steps, decisions\n- Update .wolf/STATUS.md (βœ… done / πŸš€ next quest) when a quest finishes or before suggesting /clear\n- Keep .wolf/TODO.md current β€” check off completed items and add new tasks as they surface (πŸ”₯ Now / ⏭️ Next / πŸ’‘ Later)\n- Check .wolf/anatomy.md before reading any project file\n- Check .wolf/cerebrum.md Do-Not-Repeat list before generating code\n- After writing or editing files, update .wolf/anatomy.md and append to .wolf/memory.md\n- After receiving a user correction, update .wolf/cerebrum.md immediately (Preferences, Learnings, or Do-Not-Repeat)\n- LEARN from every interaction: if you discover a convention, user preference, or project pattern, add it to .wolf/cerebrum.md. Low threshold β€” when in doubt, log it.\n- BEFORE fixing any bug or error: read .wolf/buglog.json for known fixes\n- AFTER fixing any bug, error, failed test, failed build, or user-reported problem: ALWAYS log to .wolf/buglog.json with error_message, root_cause, fix, and tags\n- If you edit a file more than twice in a session, that likely indicates a bug β€” log it to .wolf/buglog.json\n- When the user asks to change/pick/migrate UI framework: read .wolf/reframe-frameworks.md, ask decision questions, recommend a framework, then execute with the framework's prompt`, }; return templates[filename] ?? ""; } @@ -480,6 +492,8 @@ function generateTemplate(destPath: string, file: string): void { "memory.md": `# Memory\n\n> Chronological action log.\n`, "anatomy.md": `# anatomy.md\n\n> Project structure index. Pending initial scan.\n`, "STATUS.md": `# STATUS\n\n> Single source of truth for resuming work. Read this FIRST when starting a session.\n> Update at the end of every work phase so the next \`/clear\` resumes in 1 read.\n\n---\n\n## βœ… Done\n\n- (nothing yet β€” fill in as work completes)\n\n---\n\n## πŸš€ Next phase\n\n**Goal:** __\n\n### Acceptance criteria\n1. __\n\n### Files to create / edit\n- __\n\n### Open decisions\n- __\n\n---\n\n## πŸ“ Active architecture\n\n- **Stack:** __\n\n---\n\n## πŸ”§ Useful commands\n\n\`\`\`bash\n# add the most-used commands here\n\`\`\`\n`, + "TODO.md": `# TODO\n\n> Working checklist. STATUS.md = handoff ("why & where we are"); TODO.md = "what's left".\n> Keep items actionable. Check off with [x]; sweep done items into STATUS.md βœ… when a phase closes.\n\n---\n\n## πŸ”₯ Now\n\n- [ ] __\n\n## ⏭️ Next\n\n- [ ] __\n\n## πŸ’‘ Later / backlog\n\n- [ ] __\n\n## βœ… Recently done\n\n- (checked items land here; sweep into STATUS.md βœ… when a phase closes)\n`, + "ROADMAP.md": `# ROADMAP\n\n> Longer-horizon plan and backlog. STATUS.md = the current quest (keep it small); ROADMAP.md = everything planned but not active. Move an item into STATUS.md "πŸš€ Next phase" when you start it.\n\n---\n\n## 🎯 Milestones\n\n- [ ] __\n\n## 🧭 Backlog (unscheduled)\n\n- __\n\n## 🧊 Someday / maybe\n\n- __\n`, "config.json": JSON.stringify({ version: 1, openwolf: { @@ -487,12 +501,14 @@ function generateTemplate(destPath: string, file: string): void { anatomy: { auto_scan_on_init: true, rescan_interval_hours: 6, max_description_length: 100, max_files: 500, exclude_patterns: ["node_modules", ".git", "dist", "build", ".wolf", ".next", ".nuxt", "coverage", "__pycache__", ".cache", "target", ".vscode", ".idea", ".turbo", ".vercel", ".netlify", ".output", "*.min.js", "*.min.css"] }, token_audit: { enabled: true, report_frequency: "weekly", waste_threshold_percent: 15, chars_per_token_code: 3.5, chars_per_token_prose: 4.0 }, cron: { enabled: true, max_retry_attempts: 3, dead_letter_enabled: true, heartbeat_interval_minutes: 30, use_claude_p: true, api_key_env: null }, - memory: { consolidation_after_days: 7, max_entries_before_consolidation: 200 }, + memory: { consolidation_after_days: 7, max_entries_before_consolidation: 200, max_sessions: 20 }, + status: { max_sessions: 2, archive_file: "history.md" }, + todo: { enabled: true }, cerebrum: { max_tokens: 2000, reflection_frequency: "weekly" }, context: { session_digest_budget_tokens: 1500, budgets: { claude: 1500, codex: 1200, gemini: 1200, opencode: 1200, cursor: 800 } }, daemon: { port: 18790, log_level: "info" }, dashboard: { enabled: true, port: 18791, host: "127.0.0.1" }, - buglog: { auto_detect: true }, + buglog: { auto_detect: true, max_entries: 200 }, }, }, null, 2), "token-ledger.json": JSON.stringify({ version: 1, created_at: "", lifetime: { total_tokens_estimated: 0, total_reads: 0, total_writes: 0, total_sessions: 0, anatomy_hits: 0, anatomy_misses: 0, repeated_reads_blocked: 0, estimated_savings_vs_bare_cli: 0 }, sessions: [], daemon_usage: [], waste_flags: [], optimization_report: { last_generated: null, patterns: [] } }, null, 2), @@ -547,6 +563,17 @@ function seedStatus(wolfDir: string, projectRoot: string): void { writeText(statusPath, content); } +// Substitute {{PROJECT_NAME}} / {{DATE}} in a freshly created template file. +function seedTemplateVars(filePath: string, projectRoot: string): void { + if (!fs.existsSync(filePath)) return; + const projectName = detectProjectName(projectRoot) || path.basename(projectRoot); + const date = new Date().toISOString().slice(0, 10); + let content = readText(filePath); + content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName); + content = content.replace(/\{\{DATE\}\}/g, date); + writeText(filePath, content); +} + function seedIdentity(wolfDir: string, projectRoot: string): void { const projectName = detectProjectName(projectRoot); if (!projectName) return; diff --git a/src/hooks/stop.ts b/src/hooks/stop.ts index d0c776e..a065cd3 100644 --- a/src/hooks/stop.ts +++ b/src/hooks/stop.ts @@ -91,12 +91,20 @@ async function main(): Promise { const reminders = [ checkForMissingBugLogs(wolfDir, session), checkCerebrumFreshness(wolfDir, session), + checkCerebrumBudget(wolfDir), checkSemanticSummaries(wolfDir, session), + checkTodoFreshness(wolfDir, session), ].filter((r): r is string => r !== null); // Check if STATUS.md is stale relative to this session checkStatusFreshness(wolfDir, session); + // Housekeeping: keep the .wolf handoff files cheap to read. + trimStatusJournal(wolfDir); // STATUS.md journal β†’ history.md (## Session Journal) + trimMemoryLog(wolfDir); // memory.md old sessions β†’ history.md (## Action Log) + trimBuglog(wolfDir); // buglog.json old bugs β†’ buglog-archive.json + generateBuglogIndex(wolfDir); // buglog.json β†’ compact buglog.md index + // Build session entry for ledger const reads = Object.entries(session.files_read).map(([file, data]) => ({ file, @@ -265,6 +273,285 @@ function checkStatusFreshness(wolfDir: string, session: SessionData): void { } } +/** + * Nudge to keep TODO.md current. If TODO.md exists, still has unchecked items, + * and wasn't touched this session despite real code activity (3+ writes outside + * .wolf/), remind Claude to update it. Returns a reminder string (surfaced via + * additionalContext) or null. TODO.md is optional β€” absence is never an error. + */ +function checkTodoFreshness(wolfDir: string, session: SessionData): string | null { + const todoPath = path.join(wolfDir, "TODO.md"); + const codeWrites = session.files_written.filter( + (w) => !w.file.includes("/.wolf/") && !w.file.endsWith(".tmp") + ); + if (codeWrites.length < 3) return null; + + try { + const stat = fs.statSync(todoPath); + const sessionStartMs = session.started ? Date.parse(session.started) : 0; + if (!sessionStartMs) return null; + if (stat.mtimeMs >= sessionStartMs) return null; // already updated this session + + const hasOpenItems = /^\s*[-*]\s*\[ \]/m.test(fs.readFileSync(todoPath, "utf8")); + if (!hasOpenItems) return null; + + return `ACTION REQUIRED: ${codeWrites.length} files changed but .wolf/TODO.md wasn't updated this session. Check off completed items and add any new tasks so the list stays actionable.`; + } catch { + return null; // no TODO.md β€” optional feature + } +} + +/** Atomic file write (tmp + rename) so a concurrent Stop hook from another + * agent/session in the same project can never read a half-written file. */ +function atomicWrite(p: string, data: string): void { + const tmp = `${p}.tmp.${process.pid}`; + fs.writeFileSync(tmp, data); + fs.renameSync(tmp, p); +} + +const HISTORY_HEADER = + "# History Archive\n\n> Older content rotated out of STATUS.md / memory.md by the stop hook (newest first). Read only when you need history beyond the live files.\n"; + +/** Resolve the shared history file (config openwolf.status.archive_file, default history.md). */ +function historyPathOf(wolfDir: string): string { + const cfg = readJSON<{ openwolf?: { status?: { archive_file?: string } } }>( + path.join(wolfDir, "config.json"), + {} + ); + return path.join(wolfDir, cfg.openwolf?.status?.archive_file ?? "history.md"); +} + +/** + * Prepend `moved` under a `##
` heading in the shared history file + * (newest first). Creates the file / section as needed. Atomic write. + */ +function archiveToHistory(wolfDir: string, section: string, moved: string): void { + const historyPath = historyPathOf(wolfDir); + let content = fs.existsSync(historyPath) ? fs.readFileSync(historyPath, "utf8") : ""; + if (!content.startsWith("# History Archive")) { + content = HISTORY_HEADER + (content ? "\n" + content : ""); + } + const block = moved.replace(/\s+$/, "") + "\n"; + const heading = `## ${section}`; + const hIdx = content.indexOf(`\n${heading}`); + if (hIdx === -1) { + content = content.replace(/\s+$/, "") + `\n\n${heading}\n\n${block}`; + } else { + const lineEnd = content.indexOf("\n", hIdx + 1) + 1; + const rest = content.slice(lineEnd).replace(/^\n+/, ""); + content = content.slice(0, lineEnd) + "\n" + block + "\n" + rest; + } + atomicWrite(historyPath, content); +} + +/** + * Keep STATUS.md lean for cheap resumes. The leading blockquote of STATUS.md is + * a session journal ("> **SESSΓƒO N ..." / "> **SESSION N ...") that grows every + * session. Rotates all but the newest N blocks into the history file under + * "## Session Journal" (newest first). N = openwolf.status.max_sessions + * (default 2). No-op unless there are more than N blocks, so most Stops touch + * nothing. + */ +function trimStatusJournal(wolfDir: string): void { + try { + const statusPath = path.join(wolfDir, "STATUS.md"); + if (!fs.existsSync(statusPath)) return; + + const cfg = readJSON<{ openwolf?: { status?: { max_sessions?: number } } }>( + path.join(wolfDir, "config.json"), + {} + ); + const keep = Math.max(1, cfg.openwolf?.status?.max_sessions ?? 2); + // Session-block header marker: "> **SESSΓƒO N" or "> **SESSION N" (any case). + const SESS_RE = /^>\s*\*\*(SESS[ΓƒA]O|SESSION)\b/i; + + const raw = fs.readFileSync(statusPath, "utf8"); + const lines = raw.split("\n"); + + // Leading blockquote region: from the first ">" line until a section boundary. + const jStart = lines.findIndex((l) => /^>/.test(l)); + if (jStart === -1) return; + let jEnd = jStart; + while (jEnd < lines.length) { + const l = lines[jEnd]; + if (/^---/.test(l) || /^#/.test(l)) break; + if (/^>/.test(l) || /^\s*$/.test(l)) { jEnd++; continue; } + break; + } + const journal = lines.slice(jStart, jEnd); + + // Split into preamble (intro quote lines) + session blocks (newest first). + const markers = journal + .map((l, i) => (SESS_RE.test(l) ? i : -1)) + .filter((i) => i >= 0); + if (markers.length <= keep) return; // nothing to rotate + + const preamble = journal.slice(0, markers[0]); + const blocks: string[][] = []; + for (let i = 0; i < markers.length; i++) { + const s = markers[i]; + const e = i + 1 < markers.length ? markers[i + 1] : journal.length; + blocks.push(journal.slice(s, e)); + } + const keepBlocks = blocks.slice(0, keep); + const archiveBlocks = blocks.slice(keep); + + const newJournal = [...preamble, ...keepBlocks.flat()]; + while (newJournal.length && newJournal[newJournal.length - 1].trim() === "") { + newJournal.pop(); + } + const newStatus = [...lines.slice(0, jStart), ...newJournal, "", ...lines.slice(jEnd)].join("\n"); + + // History first: a crash between writes can only duplicate a block, never lose one. + archiveToHistory(wolfDir, "Session Journal", archiveBlocks.map((b) => b.join("\n")).join("\n")); + atomicWrite(statusPath, newStatus); + } catch { + // Never break the Stop hook over housekeeping. + } +} + +/** + * Keep memory.md lean. It is a chronological action log grouped into + * "## Session: " blocks, appended oldestβ†’newest (newest at the bottom). + * Rotates all but the newest N session blocks into the history file under + * "## Action Log" (newest first). N = openwolf.memory.max_sessions (default 20). + * No-op unless there are more than N blocks. + */ +function trimMemoryLog(wolfDir: string): void { + try { + const memPath = path.join(wolfDir, "memory.md"); + if (!fs.existsSync(memPath)) return; + + const cfg = readJSON<{ openwolf?: { memory?: { max_sessions?: number } } }>( + path.join(wolfDir, "config.json"), + {} + ); + const keep = Math.max(2, cfg.openwolf?.memory?.max_sessions ?? 20); + const SESSION_RE = /^##\s+Session:/i; + + const raw = fs.readFileSync(memPath, "utf8"); + const lines = raw.split("\n"); + const markers = lines + .map((l, i) => (SESSION_RE.test(l) ? i : -1)) + .filter((i) => i >= 0); + if (markers.length <= keep) return; + + const preamble = lines.slice(0, markers[0]); + const blocks: string[][] = []; + for (let i = 0; i < markers.length; i++) { + const s = markers[i]; + const e = i + 1 < markers.length ? markers[i + 1] : lines.length; + blocks.push(lines.slice(s, e)); + } + // memory.md is oldest-first: keep the LAST N blocks, archive the earlier ones. + const keepBlocks = blocks.slice(blocks.length - keep); + const olderBlocks = blocks.slice(0, blocks.length - keep); + + const newMemory = [...preamble, ...keepBlocks.flat()]; + while (newMemory.length && newMemory[newMemory.length - 1].trim() === "") newMemory.pop(); + + // Reverse so the most recent of the evicted blocks lands on top (newest first). + const moved = olderBlocks.reverse().map((b) => b.join("\n")).join("\n"); + archiveToHistory(wolfDir, "Action Log", moved); + atomicWrite(memPath, newMemory.join("\n") + "\n"); + } catch { + // Never break the Stop hook over housekeeping. + } +} + +/** + * Cap the live buglog.json at the newest N bugs (openwolf.buglog.max_entries, + * default 200); older bugs move to buglog-archive.json (chronological). Keeps + * the generated buglog.md index small without ever deleting a logged fix. Bugs + * are stored append-order (oldest first), so the newest are at the tail. + */ +function trimBuglog(wolfDir: string): void { + try { + const jsonPath = path.join(wolfDir, "buglog.json"); + if (!fs.existsSync(jsonPath)) return; + + const cfg = readJSON<{ openwolf?: { buglog?: { max_entries?: number } } }>( + path.join(wolfDir, "config.json"), + {} + ); + const keep = Math.max(20, cfg.openwolf?.buglog?.max_entries ?? 200); + + const data = readJSON<{ version?: number; bugs?: unknown[] }>(jsonPath, { bugs: [] }); + const bugs = Array.isArray(data.bugs) ? data.bugs : []; + if (bugs.length <= keep) return; + + const kept = bugs.slice(bugs.length - keep); + const older = bugs.slice(0, bugs.length - keep); + + const archPath = path.join(wolfDir, "buglog-archive.json"); + const arch = readJSON<{ version?: number; bugs?: unknown[] }>(archPath, { version: 1, bugs: [] }); + const archBugs = Array.isArray(arch.bugs) ? arch.bugs : []; + // Archive first so a crash can only duplicate, never lose, a bug. + atomicWrite(archPath, JSON.stringify({ version: arch.version ?? 1, bugs: [...archBugs, ...older] }, null, 2) + "\n"); + atomicWrite(jsonPath, JSON.stringify({ version: data.version ?? 1, bugs: kept }, null, 2) + "\n"); + } catch { + // Best-effort β€” never break the Stop hook. + } +} + +/** + * Render a compact buglog.md index from buglog.json so Claude reads a small + * index (id Β· tags Β· file Β· truncated message) before a fix, and only opens the + * full entry in buglog.json when a candidate matches. Regenerated only when the + * source is newer than the index. Non-destructive: buglog.json is untouched. + */ +function generateBuglogIndex(wolfDir: string): void { + try { + const jsonPath = path.join(wolfDir, "buglog.json"); + const mdPath = path.join(wolfDir, "buglog.md"); + if (!fs.existsSync(jsonPath)) return; + if (fs.existsSync(mdPath) && fs.statSync(mdPath).mtimeMs >= fs.statSync(jsonPath).mtimeMs) return; + + const data = readJSON<{ bugs?: Array> }>(jsonPath, { bugs: [] }); + const bugs = Array.isArray(data.bugs) ? data.bugs : []; + const esc = (s: string) => s.replace(/\s+/g, " ").replace(/\|/g, "\\|"); + const rows = bugs.map((b) => { + const id = esc(String(b.id ?? "")); + const tags = Array.isArray(b.tags) ? esc((b.tags as string[]).join(",")) : ""; + const file = esc(String(b.file ?? "")); + const msg = esc(String(b.error_message ?? "")).slice(0, 80); + return `| ${id} | ${tags} | ${file} | ${msg} |`; + }); + const md = + `# Buglog Index\n\n> Compact index of .wolf/buglog.json (${bugs.length} bugs). Auto-generated by the stop hook β€” do not edit.\n` + + `> BEFORE fixing a bug, scan this index by tag / file / message, then open ONLY the matching entry in buglog.json. Never read the whole JSON.\n\n` + + `| id | tags | file | error (truncated) |\n|---|---|---|---|\n` + + rows.join("\n") + "\n"; + atomicWrite(mdPath, md); + } catch { + // Index generation is best-effort. + } +} + +/** + * Warn when cerebrum.md has grown well past its token budget. cerebrum.md is + * curated knowledge read before every code-gen, so it is NOT auto-trimmed + * (that would risk dropping real learnings); instead Claude is nudged to + * consolidate it by hand. Returns a reminder or null. + */ +function checkCerebrumBudget(wolfDir: string): string | null { + try { + const cfg = readJSON<{ openwolf?: { cerebrum?: { max_tokens?: number } } }>( + path.join(wolfDir, "config.json"), + {} + ); + const budget = cfg.openwolf?.cerebrum?.max_tokens ?? 2000; + const bytes = fs.statSync(path.join(wolfDir, "cerebrum.md")).size; + const estTokens = Math.round(bytes / 4); + if (estTokens > budget * 2) { + return `ACTION REQUIRED: cerebrum.md is ~${estTokens} tokens, well over its ${budget}-token budget, and it's read before every code-gen. Consolidate it: merge duplicate learnings, drop stale/one-off entries, and move dated Decision Log items into history. Keep all active User Preferences and Do-Not-Repeat rules.`; + } + } catch { + // no cerebrum.md β€” fine + } + return null; +} + /** * Check if cerebrum.md was updated recently. If it hasn't been updated in * a while and there was significant activity, return a reminder. diff --git a/src/templates/OPENWOLF.md b/src/templates/OPENWOLF.md index 4af783c..dde65aa 100644 --- a/src/templates/OPENWOLF.md +++ b/src/templates/OPENWOLF.md @@ -23,6 +23,21 @@ You are working in an OpenWolf-managed project. These rules apply every turn. **The bar is HIGH for STATUS.md.** Stale STATUS.md = wasted next session. Always treat it as the handoff document. +**Session journal auto-rotation.** If you keep a running session journal at the top of STATUS.md (a leading blockquote of `> **SESSION N ...` / `> **SESSΓƒO N ...` blocks), the stop hook automatically keeps only the newest `openwolf.status.max_sessions` blocks (default 2) and moves older ones to `.wolf/history.md` (under `## Session Journal`). Just prepend a new block per session β€” you don't trim by hand. Read `history.md` only when you need older context. + +**Big-picture planning lives in ROADMAP.md.** Keep STATUS.md focused on the *current* quest. Anything planned-but-not-active (milestones, backlog, someday ideas) goes in `.wolf/ROADMAP.md`. When you start a backlog item, move it into STATUS.md `πŸš€ Next phase`. + +## TODO.md β€” Working Checklist + +`.wolf/TODO.md` is the actionable task list that complements STATUS.md: +- **STATUS.md** = handoff narrative ("why & where we are", decisions, next quest). +- **TODO.md** = the checklist ("what's left"), grouped into `πŸ”₯ Now`, `⏭️ Next`, `πŸ’‘ Later`, `βœ… Recently done`. + +**Keep TODO.md current:** +1. When you start a task, move it to `πŸ”₯ Now`. When you finish one, check it off (`[x]`) into `βœ… Recently done`. +2. When the user asks for new work, add it under `⏭️ Next` or `πŸ’‘ Later`. +3. When a phase closes, sweep `βœ… Recently done` items into STATUS.md `βœ… Done` and clear the TODO list. + ## File Navigation 1. Check `.wolf/anatomy.md` BEFORE reading any file. It has a 2-3 line description and token estimate for every file in the project. @@ -85,7 +100,7 @@ OpenWolf's value comes from learning across sessions. You MUST update `.wolf/cer - You change error handling, try/catch blocks, or validation logic - The user says something "doesn't work", "is broken", or "shows wrong X" -**Before fixing:** Read `.wolf/buglog.json` first β€” the fix may already be known. +**Before fixing:** scan `.wolf/buglog.md` first β€” a compact auto-generated index (id Β· tags Β· file Β· message) of every recent logged bug. Find a candidate by tag/file/message, then open ONLY that entry in `.wolf/buglog.json`. Do NOT read the whole `buglog.json` β€” it grows large and the index exists precisely to avoid that. Older bugs beyond `openwolf.buglog.max_entries` are moved to `.wolf/buglog-archive.json` (rarely needed; check it only if the index has no match). **After fixing:** ALWAYS append to `.wolf/buglog.json` with this structure: ```json @@ -153,5 +168,6 @@ When the user asks to change, pick, migrate, or "reframe" their project's UI fra Before ending or when asked to wrap up: 1. **Update `.wolf/STATUS.md`** β€” move concluded work to βœ…, write next quest in πŸš€, bump date. This is the most important step for next session efficiency. -2. Write a session summary to `.wolf/memory.md`. -3. Review the session: did you learn anything? Did the user correct you? Did you fix a bug? If yes, update `.wolf/cerebrum.md` and/or `.wolf/buglog.json`. +2. **Update `.wolf/TODO.md`** β€” check off what you finished, add anything new that surfaced. +3. Write a session summary to `.wolf/memory.md`. +4. Review the session: did you learn anything? Did the user correct you? Did you fix a bug? If yes, update `.wolf/cerebrum.md` and/or `.wolf/buglog.json`. diff --git a/src/templates/ROADMAP.md b/src/templates/ROADMAP.md new file mode 100644 index 0000000..3a029b7 --- /dev/null +++ b/src/templates/ROADMAP.md @@ -0,0 +1,20 @@ +# ROADMAP β€” {{PROJECT_NAME}} + +> Longer-horizon plan and backlog. **STATUS.md** holds the *current* quest (keep it small); +> **ROADMAP.md** holds everything planned but not active. When you start a backlog item, +> move it into STATUS.md "πŸš€ Next phase". When a milestone ships, check it off here. +> Last updated: {{DATE}} + +--- + +## 🎯 Milestones + +- [ ] __ + +## 🧭 Backlog (unscheduled) + +- __ + +## 🧊 Someday / maybe + +- __ diff --git a/src/templates/TODO.md b/src/templates/TODO.md new file mode 100644 index 0000000..f1dde2e --- /dev/null +++ b/src/templates/TODO.md @@ -0,0 +1,25 @@ +# TODO β€” {{PROJECT_NAME}} + +> Working checklist. **STATUS.md** = handoff ("why & where we are"); **TODO.md** = "what's left to do". +> Keep items actionable and short. Check off with `[x]`; sweep done items into STATUS.md βœ… when a phase closes. +> Last updated: {{DATE}} + +--- + +## πŸ”₯ Now (this session) + +- [ ] __ + +## ⏭️ Next (queued, ready to pick up) + +- [ ] __ + +## πŸ’‘ Later / backlog (not scheduled) + +- [ ] __ + +## βœ… Recently done + + + +- (nothing yet) diff --git a/src/templates/claude-rules-openwolf.md b/src/templates/claude-rules-openwolf.md index db16288..2a48c5d 100644 --- a/src/templates/claude-rules-openwolf.md +++ b/src/templates/claude-rules-openwolf.md @@ -5,12 +5,14 @@ globs: **/* - Read .wolf/STATUS.md FIRST when resuming a session β€” it contains current quest, next steps, decisions - Update .wolf/STATUS.md (βœ… done / πŸš€ next quest) when a quest finishes or before suggesting /clear +- Keep .wolf/TODO.md current β€” check off completed items and add new tasks as they surface (πŸ”₯ Now / ⏭️ Next / πŸ’‘ Later) +- Keep STATUS.md focused on the current quest; put planned-but-not-active work in .wolf/ROADMAP.md - Check .wolf/anatomy.md before reading any project file - Check .wolf/cerebrum.md Do-Not-Repeat list before generating code - After writing or editing files, update .wolf/anatomy.md and append to .wolf/memory.md - After receiving a user correction, update .wolf/cerebrum.md immediately (Preferences, Learnings, or Do-Not-Repeat) - LEARN from every interaction: if you discover a convention, user preference, or project pattern, add it to .wolf/cerebrum.md. Low threshold β€” when in doubt, log it. -- BEFORE fixing any bug or error: read .wolf/buglog.json for known fixes +- BEFORE fixing any bug or error: scan .wolf/buglog.md (compact index), then open only the matching entry in .wolf/buglog.json β€” never read the whole JSON - AFTER fixing any bug, error, failed test, failed build, or user-reported problem: ALWAYS log to .wolf/buglog.json with error_message, root_cause, fix, and tags - If you edit a file more than twice in a session, that likely indicates a bug β€” log it to .wolf/buglog.json - When the user asks to change/pick/migrate UI framework: read .wolf/reframe-frameworks.md, ask decision questions, recommend a framework, then execute with the framework's prompt diff --git a/src/templates/config.json b/src/templates/config.json index 3a26787..915e240 100644 --- a/src/templates/config.json +++ b/src/templates/config.json @@ -46,7 +46,19 @@ }, "memory": { "consolidation_after_days": 7, - "max_entries_before_consolidation": 200 + "max_entries_before_consolidation": 200, + "max_sessions": 20 + }, + "status": { + "max_sessions": 2, + "archive_file": "history.md" + }, + "todo": { + "enabled": true + }, + "buglog": { + "auto_detect": true, + "max_entries": 200 }, "cerebrum": { "max_tokens": 2000,