chore(wolf): checkpoint buglog.json (1062 bugs)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
This commit is contained in:
Rongsong Shen 2026-08-27 08:27:43 +00:00
parent 13246a89c8
commit d8bf8fb2e1
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
27 changed files with 7702 additions and 897158 deletions

View file

@ -1,65 +1,181 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, parseAnatomy, readStdin, normalizePath } from "./shared.js";
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readStdin, normalizePath, getProjectDir, emitHookJSON, recordInjection, hookMain, getSessionFilePath } from "./shared.js";
import { lookupEntry } from "./anatomy-store.js";
function duplicateMode(wolfDir) {
const cfg = readJSON(path.join(wolfDir, "config.json"), {});
const mode = cfg.openwolf?.reads?.duplicate_mode;
return mode === "deny" || mode === "off" ? mode : "warn";
}
function skeletonHintsEnabled(wolfDir) {
const cfg = readJSON(path.join(wolfDir, "config.json"), {});
return cfg.openwolf?.reads?.skeleton_hints !== false;
}
async function main() {
ensureWolfDir();
const wolfDir = getWolfDir();
const hooksDir = path.join(wolfDir, "hooks");
const sessionFile = path.join(hooksDir, "_session.json");
const raw = await readStdin();
let input;
try {
input = JSON.parse(raw);
}
catch {
process.exit(0);
return;
}
const sessionFile = getSessionFilePath(input);
const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
if (!filePath) {
process.exit(0);
return;
}
// Ranged reads (offset/limit) are exactly what the symbol hints steer the
// model toward — never warn about, deny, or record them as full reads (a
// ranged first contact must not make a later legitimate full read look like
// a duplicate).
const isRangedRead = input.tool_input?.offset !== undefined || input.tool_input?.limit !== undefined;
// Subagents share this session file but not the main thread's context: a
// file the main thread read is unseen by a fresh subagent, so duplicate
// handling stays hands-off for them.
const isSubagent = typeof input.agent_id === "string" && input.agent_id.length > 0;
const normalizedFile = normalizePath(filePath);
// Skip tracking for .wolf/ internal files — they're infrastructure, not project files.
// Counting them inflates anatomy miss rates since .wolf/ is excluded from anatomy scanning.
const projectDir = normalizePath(process.env.CLAUDE_PROJECT_DIR || process.cwd());
const projectDir = normalizePath(getProjectDir());
const relToProject = normalizedFile.startsWith(projectDir)
? normalizedFile.slice(projectDir.length).replace(/^\//, "")
: "";
if (relToProject.startsWith(".wolf/") || relToProject.startsWith(".wolf\\")) {
process.exit(0);
// 2.4: .wolf reads used to be invisible to OpenWolf itself (~147k tokens
// of unmeasured self-inflicted reads, including whole-file reads of the
// two files the rules say never to read whole). Measure them (tagged
// separately so anatomy metrics stay clean) and steer the expensive
// pattern to the cheap one, once per file per session.
try {
const base = path.basename(normalizedFile);
const isRanged = input.tool_input?.offset !== undefined || input.tool_input?.limit !== undefined;
if (!isRanged && (base === "anatomy.md" || base === "cerebrum.md" || base === "memory.md")) {
let sizeTokens = 0;
try {
sizeTokens = Math.ceil(fs.statSync(filePath).size / 3.5);
}
catch { }
if (sizeTokens > 1500) {
const session = readJSON(sessionFile, {
session_id: "", files_read: {}, anatomy_hits: 0, anatomy_misses: 0, repeated_reads_warned: 0,
});
const warned = (session.wolf_read_advised ?? {});
if (!warned[base]) {
warned[base] = true;
session.wolf_read_advised = warned;
const note = base === "anatomy.md"
? `OpenWolf: ${base} is ~${sizeTokens} tokens and is an index, not a document. \`openwolf find <symbol or path>\` answers location queries in under 1k tokens; grep it for a single path's line otherwise.`
: `OpenWolf: ${base} is ~${sizeTokens} tokens. Grep the section you need (for example "## Do-Not-Repeat") or read with offset/limit; the whole file rarely pays for itself.`;
recordInjection(session, "wolf_read_advice", note);
writeJSON(sessionFile, session);
emitHookJSON("PreToolUse", { additionalContext: note });
}
}
}
}
catch { }
return;
}
const session = readJSON(sessionFile, {
session_id: "", files_read: {}, anatomy_hits: 0, anatomy_misses: 0,
repeated_reads_warned: 0,
});
// Check if already read this session
if (session.files_read[normalizedFile]) {
const prev = session.files_read[normalizedFile];
process.stderr.write(`⚡ OpenWolf: ${path.basename(normalizedFile)} was already read this session (~${prev.tokens} tokens). Consider using your existing knowledge of this file.\n`);
session.files_read[normalizedFile].count++;
session.repeated_reads_warned++;
writeJSON(sessionFile, session);
process.exit(0);
if (isRangedRead) {
// Record the contact so dedupe knows about it, but flagged: a ranged
// first contact must never make a later full read look like a duplicate
// (the old asymmetry with post-read inflated warnings ~20x).
if (!session.files_read[normalizedFile]) {
session.files_read[normalizedFile] = {
count: 1, tokens: 0, first_read: new Date().toISOString(), ranged: true,
};
writeJSON(sessionFile, session);
}
return;
}
// Check anatomy.md for this file
const anatomyContent = readMarkdown(path.join(wolfDir, "anatomy.md"));
const sections = parseAnatomy(anatomyContent);
let found = false;
for (const [sectionKey, entries] of sections) {
for (const entry of entries) {
// Build the full relative path from the section key + filename for accurate matching
const entryRelPath = normalizePath(path.join(sectionKey, entry.file));
if (normalizedFile.endsWith(entryRelPath) || normalizedFile.endsWith("/" + entryRelPath)) {
process.stderr.write(`📋 OpenWolf anatomy: ${entry.file} — ${entry.description} (~${entry.tokens} tok)\n`);
found = true;
break;
// Model-visible notes for this run — flushed as ONE additionalContext at exit.
const notes = [];
// Check if already read this session. Only a prior FULL read counts —
// ranged-only contact means the model has never seen the whole file.
if (session.files_read[normalizedFile] && session.files_read[normalizedFile].ranged !== true) {
const prev = session.files_read[normalizedFile];
let modifiedSinceRead = true;
try {
const mtime = fs.statSync(filePath).mtimeMs;
modifiedSinceRead = prev.read_mtime === undefined || mtime > prev.read_mtime;
}
catch { }
if (!modifiedSinceRead) {
const mode = duplicateMode(wolfDir);
session.repeated_reads_warned++;
// Denial is the only path that actually saves the tokens, but it must
// never strand the model: full reads only (ranged reads exited above),
// main thread only, only when the earlier read verifiably delivered
// content (tokens > 0), never after a compaction evicted that content,
// and only ONCE per file per session — the next attempt passes through.
const denyEligible = mode === "deny" && !isSubagent && prev.tokens > 0 &&
prev.denied_once !== true && prev.compacted !== true;
if (denyEligible) {
prev.denied_once = true;
session.reads_denied = (session.reads_denied ?? 0) + 1;
session.denied_tokens_saved = (session.denied_tokens_saved ?? 0) + prev.tokens;
const reason = `OpenWolf: ${path.basename(normalizedFile)} was already read this session (~${prev.tokens} tok) and is unchanged on disk. Reuse your earlier read, or use offset/limit for the exact lines you need. If you do need the full file again, a second attempt will pass through.`;
recordInjection(session, "dup_deny", reason);
writeJSON(sessionFile, session);
emitHookJSON("PreToolUse", {
permissionDecision: "deny",
permissionDecisionReason: reason,
});
return;
}
prev.count++;
if (mode !== "off") {
notes.push(`OpenWolf: ${path.basename(normalizedFile)} was already read this session (~${prev.tokens} tok), unchanged since. If you only need the gist, your earlier read may suffice; for exact text (edit anchors, line numbers), the re-read is fine.`);
}
if (notes.length > 0)
recordInjection(session, "dup_warn", notes.join("\n"));
writeJSON(sessionFile, session);
if (notes.length > 0) {
emitHookJSON("PreToolUse", { additionalContext: notes.join("\n") });
}
return;
}
delete session.files_read[normalizedFile];
}
// Anatomy lookup: O(1) against the durable store, legacy md scan fallback.
const entry = lookupEntry(wolfDir, projectDir, normalizedFile);
const found = entry !== null;
if (entry) {
if (entry.description) {
notes.push(`OpenWolf anatomy: ${entry.file}: ${entry.description} (~${entry.tokens} tok)`);
}
// Symbol hint (F2b Phase B): point at slices of big files. Suppressed if
// the on-disk file no longer matches what was indexed — a stale line
// range that misdirects an offset read is worse than no hint at all.
if (entry.symbols && entry.symbols.length > 0) {
let fresh = false;
try {
const st = fs.statSync(filePath);
fresh = (entry.size === undefined || st.size === entry.size) &&
(entry.mtimeMs === undefined || Math.abs(st.mtimeMs - entry.mtimeMs) < 1);
}
catch { }
if (fresh) {
// J2 skeleton hint: for big files with an indexed signature outline,
// the outline replaces the "largest sections" line — often enough to
// skip the full read entirely. Config-gated (reads.skeleton_hints).
if (entry.skeleton && entry.tokens > 2000 && skeletonHintsEnabled(wolfDir)) {
notes.push(`Signature outline of ${entry.file} (~${entry.tokens} tok full; symbols with line ranges follow):\n${entry.skeleton}\nRead with offset/limit to fetch just the part you need.`);
}
else {
const top = [...entry.symbols].sort((a, b) => b.tokens - a.tokens).slice(0, 5);
const list = top.map((s) => `${s.kind} ${s.name} L${s.startLine}-${s.endLine} ~${s.tokens} tok`).join("; ");
notes.push(`Largest sections: ${list}. Read with offset/limit to fetch just the part you need.`);
}
}
}
if (found)
break;
}
if (found) {
session.anatomy_hits++;
@ -68,13 +184,24 @@ async function main() {
session.anatomy_misses++;
}
// Record initial read entry (tokens will be updated in post-read)
let readMtime;
try {
readMtime = fs.statSync(filePath).mtimeMs;
}
catch { }
session.files_read[normalizedFile] = {
count: 1,
tokens: 0,
first_read: new Date().toISOString(),
read_mtime: readMtime,
anatomy_hit: found,
};
if (notes.length > 0)
recordInjection(session, "anatomy_hint", notes.join("\n"));
writeJSON(sessionFile, session);
process.exit(0);
if (notes.length > 0) {
emitHookJSON("PreToolUse", { additionalContext: notes.join("\n") });
}
}
main().catch(() => process.exit(0));
hookMain("pre-read", main);
//# sourceMappingURL=pre-read.js.map