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:
parent
13246a89c8
commit
d8bf8fb2e1
27 changed files with 7702 additions and 897158 deletions
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue