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,33 +1,75 @@
import * as path from "node:path";
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, parseAnatomy, estimateTokens, readStdin, normalizePath } from "./shared.js";
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, estimateTokens, readStdin, normalizePath, getProjectDir, hookMain, getSessionFilePath } from "./shared.js";
import { lookupEntry } from "./anatomy-store.js";
/**
* The PostToolUse payload carries the tool result in `tool_response`, whose
* shape depends on the tool and harness version: a plain string, an array of
* content blocks, or a structured object ({content} or {file:{content}}).
* Older builds of this hook read a `tool_output` field that never existed in
* Claude Code's payload, so read-token tracking was always zero (issue: the
* ledger under-reported every session).
*/
function extractToolResponseText(resp) {
if (typeof resp === "string")
return resp;
if (Array.isArray(resp)) {
return resp
.map((block) => (block && typeof block === "object" && typeof block.text === "string" ? block.text : ""))
.join("");
}
if (resp && typeof resp === "object") {
const obj = resp;
if (typeof obj.content === "string")
return obj.content;
if (obj.file && typeof obj.file.content === "string")
return obj.file.content;
}
return "";
}
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 ?? "";
const content = input.tool_output?.content ?? "";
const content = extractToolResponseText(input.tool_response) || input.tool_output?.content || "";
if (!filePath) {
process.exit(0);
return;
}
// Ranged reads: pre-read already recorded the contact with ranged:true.
// Registering them as full reads here is what used to make a later
// legitimate full read look like a duplicate (~20x warning inflation).
if (input.tool_input?.offset !== undefined || input.tool_input?.limit !== undefined) {
return;
}
const normalizedFile = normalizePath(filePath);
// Skip tracking for .wolf/ internal files — consistent with pre-read
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: measure OpenWolf's own context cost instead of hiding it. Tagged
// separately from project reads so anatomy hit-rates stay meaningful.
try {
if (content) {
const session = readJSON(sessionFile, { files_read: {} });
const tok = estimateTokens(content, "prose");
session.wolf_internal_tokens = (session.wolf_internal_tokens ?? 0) + tok;
const perFile = (session.wolf_internal_reads ?? {});
perFile[relToProject] = (perFile[relToProject] ?? 0) + tok;
session.wolf_internal_reads = perFile;
writeJSON(sessionFile, session);
}
}
catch { }
return;
}
const ext = path.extname(filePath).toLowerCase();
@ -35,35 +77,26 @@ async function main() {
const proseExts = new Set([".md", ".txt", ".rst"]);
const type = codeExts.has(ext) ? "code" : proseExts.has(ext) ? "prose" : "mixed";
let tokens = content ? estimateTokens(content, type) : 0;
// Fallback: if tool_output had no content, use anatomy token estimate
// Fallback: if tool_output had no content, use the anatomy token estimate
if (tokens === 0) {
const anatomyContent = readMarkdown(path.join(wolfDir, "anatomy.md"));
const sections = parseAnatomy(anatomyContent);
for (const [sectionKey, entries] of sections) {
for (const entry of entries) {
const entryRelPath = normalizePath(path.join(sectionKey, entry.file));
if (normalizedFile.endsWith(entryRelPath) || normalizedFile.endsWith("/" + entryRelPath)) {
tokens = entry.tokens;
break;
}
}
if (tokens > 0)
break;
}
const entry = lookupEntry(wolfDir, projectDir, normalizedFile);
if (entry)
tokens = entry.tokens;
}
const session = readJSON(sessionFile, { files_read: {} });
if (session.files_read[normalizedFile]) {
session.files_read[normalizedFile].tokens = tokens;
const existing = session.files_read[normalizedFile];
if (existing && existing.ranged !== true) {
existing.tokens = tokens;
}
else {
// Fresh full read (or an upgrade of a ranged-only contact to a full read).
session.files_read[normalizedFile] = {
count: 1,
tokens,
first_read: new Date().toISOString(),
first_read: existing?.first_read ?? new Date().toISOString(),
};
}
writeJSON(sessionFile, session);
process.exit(0);
}
main().catch(() => process.exit(0));
hookMain("post-read", main);
//# sourceMappingURL=post-read.js.map