From b116681529477440ca7cb55505fd5503e1b38b91 Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Mon, 1 Dec 2025 17:43:04 -0500 Subject: [PATCH] refactor: improve context economics display logic and logging - Updated logging category from 'CONTEXT' to 'HOOK' for context settings loading failure. - Simplified the display logic for the Context Economics section to show only when relevant settings are enabled. - Enhanced readability by consolidating repeated code for displaying token savings. - Adjusted footer logic to conditionally display token savings message based on visibility of context economics. --- plugin/scripts/context-hook.js | 18 +++---- src/hooks/context-hook.ts | 98 +++++++++++++++++++--------------- 2 files changed, 63 insertions(+), 53 deletions(-) diff --git a/plugin/scripts/context-hook.js b/plugin/scripts/context-hook.js index 22ba3ebb..b458a8f0 100755 --- a/plugin/scripts/context-hook.js +++ b/plugin/scripts/context-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -import U from"path";import{homedir as Ie}from"os";import{existsSync as ve,readFileSync as ye,unlinkSync as Le}from"fs";import{stdin as Z}from"process";import{fileURLToPath as Ae}from"url";import{dirname as Ce}from"path";import Ne from"better-sqlite3";import{join as f,dirname as be,basename as He}from"path";import{homedir as ne}from"os";import{existsSync as Ke,mkdirSync as fe}from"fs";import{fileURLToPath as Re}from"url";function Oe(){return typeof __dirname<"u"?__dirname:be(Re(import.meta.url))}var Je=Oe(),v=process.env.CLAUDE_MEM_DATA_DIR||f(ne(),".claude-mem"),K=process.env.CLAUDE_CONFIG_DIR||f(ne(),".claude"),Qe=f(v,"archives"),ze=f(v,"logs"),Ze=f(v,"trash"),es=f(v,"backups"),ss=f(v,"settings.json"),oe=f(v,"claude-mem.db"),ts=f(v,"vector-db"),rs=f(K,"settings.json"),ns=f(K,"commands"),os=f(K,"CLAUDE.md");function ie(u){fe(u,{recursive:!0})}var q=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(q||{}),J=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=q[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +import U from"path";import{homedir as ve}from"os";import{existsSync as ye,readFileSync as Ae,unlinkSync as Le}from"fs";import{stdin as Z}from"process";import{fileURLToPath as Ce}from"url";import{dirname as De}from"path";import Ie from"better-sqlite3";import{join as f,dirname as fe,basename as Ge}from"path";import{homedir as ie}from"os";import{existsSync as qe,mkdirSync as Re}from"fs";import{fileURLToPath as Oe}from"url";function Ne(){return typeof __dirname<"u"?__dirname:fe(Oe(import.meta.url))}var Qe=Ne(),y=process.env.CLAUDE_MEM_DATA_DIR||f(ie(),".claude-mem"),K=process.env.CLAUDE_CONFIG_DIR||f(ie(),".claude"),ze=f(y,"archives"),Ze=f(y,"logs"),es=f(y,"trash"),ss=f(y,"backups"),ts=f(y,"settings.json"),ae=f(y,"claude-mem.db"),rs=f(y,"vector-db"),ns=f(K,"settings.json"),os=f(K,"commands"),is=f(K,"CLAUDE.md");function de(u){Re(u,{recursive:!0})}var q=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(q||{}),J=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=q[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e0&&(S=` {${Object.entries(p).map(([L,D])=>`${L}=${D}`).join(", ")}}`)}let y=`[${d}] [${c}] [${l}] ${g}${t}${S}${h}`;e===3?console.error(y):console.log(y)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},P=new J;var B=class{db;constructor(){ie(v),this.db=new Ne(oe),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable(),this.ensureDiscoveryTokensColumn()}initializeSchema(){try{this.db.exec(` +`+JSON.stringify(i,null,2):h=" "+this.formatData(i));let S="";if(r){let{sessionId:R,sdkSessionId:n,correlationId:m,...p}=r;Object.keys(p).length>0&&(S=` {${Object.entries(p).map(([L,$])=>`${L}=${$}`).join(", ")}}`)}let A=`[${d}] [${c}] [${l}] ${g}${t}${S}${h}`;e===3?console.error(A):console.log(A)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},W=new J;var j=class{db;constructor(){de(y),this.db=new Ie(ae),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable(),this.ensureDiscoveryTokensColumn()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, @@ -304,7 +304,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? AND sdk_session_id IS NULL - `).run(s,e).changes===0?(P.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` + `).run(s,e).changes===0?(W.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` UPDATE sdk_sessions SET worker_port = ? WHERE id = ? @@ -401,13 +401,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje FROM session_summaries WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${d} ORDER BY created_at_epoch ASC - `,y=` + `,A=` SELECT up.*, s.project, s.sdk_session_id FROM user_prompts up JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${d.replace("project","s.project")} ORDER BY up.created_at_epoch ASC - `;try{let R=this.db.prepare(h).all(l,g,...c),n=this.db.prepare(S).all(l,g,...c),m=this.db.prepare(y).all(l,g,...c);return{observations:R,sessions:n.map(p=>({id:p.id,sdk_session_id:p.sdk_session_id,project:p.project,request:p.request,completed:p.completed,next_steps:p.next_steps,created_at:p.created_at,created_at_epoch:p.created_at_epoch})),prompts:m.map(p=>({id:p.id,claude_session_id:p.claude_session_id,project:p.project,prompt:p.prompt_text,created_at:p.created_at,created_at_epoch:p.created_at_epoch}))}}catch(R){return console.error("[SessionStore] Error querying timeline records:",R.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};var Q=["bugfix","feature","refactor","discovery","decision","change"],z=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"],ae={bugfix:"\u{1F534}",feature:"\u{1F7E3}",refactor:"\u{1F504}",change:"\u2705",discovery:"\u{1F535}",decision:"\u2696\uFE0F","session-request":"\u{1F3AF}"},de={discovery:"\u{1F50D}",change:"\u{1F6E0}\uFE0F",feature:"\u{1F6E0}\uFE0F",bugfix:"\u{1F6E0}\uFE0F",refactor:"\u{1F6E0}\uFE0F",decision:"\u2696\uFE0F"},ce=Q.join(","),pe=z.join(",");var De=Ae(import.meta.url),ke=Ce(De),$e=U.join(ke,"../../.install-version");function xe(){let u={totalObservationCount:parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10),fullObservationCount:5,sessionCount:10,showReadTokens:!0,showWorkTokens:!0,showSavingsAmount:!0,showSavingsPercent:!0,observationTypes:new Set(Q),observationConcepts:new Set(z),fullObservationField:"narrative",showLastSummary:!0,showLastMessage:!1};try{let e=U.join(Ie(),".claude","settings.json");if(!ve(e))return u;let t=JSON.parse(ye(e,"utf-8")).env||{};return{totalObservationCount:parseInt(t.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10),fullObservationCount:parseInt(t.CLAUDE_MEM_CONTEXT_FULL_COUNT||"5",10),sessionCount:parseInt(t.CLAUDE_MEM_CONTEXT_SESSION_COUNT||"10",10),showReadTokens:t.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS!=="false",showWorkTokens:t.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS!=="false",showSavingsAmount:t.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT!=="false",showSavingsPercent:t.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT!=="false",observationTypes:new Set((t.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES||ce).split(",").map(r=>r.trim()).filter(Boolean)),observationConcepts:new Set((t.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS||pe).split(",").map(r=>r.trim()).filter(Boolean)),fullObservationField:t.CLAUDE_MEM_CONTEXT_FULL_FIELD||"narrative",showLastSummary:t.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY!=="false",showLastMessage:t.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE==="true"}}catch(e){return P.warn("CONTEXT","Failed to load context settings, using defaults",{},e),u}}var ue=4,Me=1,o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m",red:"\x1B[31m"};function le(u){if(!u)return[];try{let e=JSON.parse(u);return Array.isArray(e)?e:[]}catch{return[]}}function Ue(u){return new Date(u).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function we(u){return new Date(u).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function Fe(u){return new Date(u).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function Xe(u,e){return U.isAbsolute(u)?U.relative(e,u):u}function W(u,e,s,t){return e?t?[`${s}${u}:${o.reset} ${e}`,""]:[`**${u}**: ${e}`,""]:[]}async function _e(u,e=!1){let s=xe(),t=u?.cwd??process.cwd(),r=t?U.basename(t):"unknown-project",i;try{i=new B}catch(m){if(m.code==="ERR_DLOPEN_FAILED"){try{Le($e)}catch{}console.error("\u26A0\uFE0F Native module rebuild needed - restart Claude Code to auto-fix"),console.error(" (This happens after Node.js version upgrades)"),process.exit(0)}throw m}let d=Array.from(s.observationTypes),c=d.map(()=>"?").join(","),l=Array.from(s.observationConcepts),g=l.map(()=>"?").join(","),h=i.db.prepare(` + `;try{let R=this.db.prepare(h).all(l,g,...c),n=this.db.prepare(S).all(l,g,...c),m=this.db.prepare(A).all(l,g,...c);return{observations:R,sessions:n.map(p=>({id:p.id,sdk_session_id:p.sdk_session_id,project:p.project,request:p.request,completed:p.completed,next_steps:p.next_steps,created_at:p.created_at,created_at_epoch:p.created_at_epoch})),prompts:m.map(p=>({id:p.id,claude_session_id:p.claude_session_id,project:p.project,prompt:p.prompt_text,created_at:p.created_at,created_at_epoch:p.created_at_epoch}))}}catch(R){return console.error("[SessionStore] Error querying timeline records:",R.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};var Q=["bugfix","feature","refactor","discovery","decision","change"],z=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"],ce={bugfix:"\u{1F534}",feature:"\u{1F7E3}",refactor:"\u{1F504}",change:"\u2705",discovery:"\u{1F535}",decision:"\u2696\uFE0F","session-request":"\u{1F3AF}"},pe={discovery:"\u{1F50D}",change:"\u{1F6E0}\uFE0F",feature:"\u{1F6E0}\uFE0F",bugfix:"\u{1F6E0}\uFE0F",refactor:"\u{1F6E0}\uFE0F",decision:"\u2696\uFE0F"},ue=Q.join(","),le=z.join(",");var ke=Ce(import.meta.url),$e=De(ke),xe=U.join($e,"../../.install-version");function Me(){let u={totalObservationCount:parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10),fullObservationCount:5,sessionCount:10,showReadTokens:!0,showWorkTokens:!0,showSavingsAmount:!0,showSavingsPercent:!0,observationTypes:new Set(Q),observationConcepts:new Set(z),fullObservationField:"narrative",showLastSummary:!0,showLastMessage:!1};try{let e=U.join(ve(),".claude","settings.json");if(!ye(e))return u;let t=JSON.parse(Ae(e,"utf-8")).env||{};return{totalObservationCount:parseInt(t.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10),fullObservationCount:parseInt(t.CLAUDE_MEM_CONTEXT_FULL_COUNT||"5",10),sessionCount:parseInt(t.CLAUDE_MEM_CONTEXT_SESSION_COUNT||"10",10),showReadTokens:t.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS!=="false",showWorkTokens:t.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS!=="false",showSavingsAmount:t.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT!=="false",showSavingsPercent:t.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT!=="false",observationTypes:new Set((t.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES||ue).split(",").map(r=>r.trim()).filter(Boolean)),observationConcepts:new Set((t.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS||le).split(",").map(r=>r.trim()).filter(Boolean)),fullObservationField:t.CLAUDE_MEM_CONTEXT_FULL_FIELD||"narrative",showLastSummary:t.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY!=="false",showLastMessage:t.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE==="true"}}catch(e){return W.warn("HOOK","Failed to load context settings, using defaults",{},e),u}}var _e=4,we=1,o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m",red:"\x1B[31m"};function me(u){if(!u)return[];try{let e=JSON.parse(u);return Array.isArray(e)?e:[]}catch{return[]}}function Ue(u){return new Date(u).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function Fe(u){return new Date(u).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function Xe(u){return new Date(u).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function Pe(u,e){return U.isAbsolute(u)?U.relative(e,u):u}function H(u,e,s,t){return e?t?[`${s}${u}:${o.reset} ${e}`,""]:[`**${u}**: ${e}`,""]:[]}async function Ee(u,e=!1){let s=Me(),t=u?.cwd??process.cwd(),r=t?U.basename(t):"unknown-project",i;try{i=new j}catch(m){if(m.code==="ERR_DLOPEN_FAILED"){try{Le(xe)}catch{}console.error("\u26A0\uFE0F Native module rebuild needed - restart Claude Code to auto-fix"),console.error(" (This happens after Node.js version upgrades)"),process.exit(0)}throw m}let d=Array.from(s.observationTypes),c=d.map(()=>"?").join(","),l=Array.from(s.observationConcepts),g=l.map(()=>"?").join(","),h=i.db.prepare(` SELECT id, sdk_session_id, type, title, subtitle, narrative, facts, concepts, files_read, files_modified, discovery_tokens, @@ -427,13 +427,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(r,s.sessionCount+Me);if(h.length===0&&S.length===0)return i.close(),e?` + `).all(r,s.sessionCount+we);if(h.length===0&&S.length===0)return i.close(),e?` ${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset} ${o.gray}${"\u2500".repeat(60)}${o.reset} ${o.dim}No previous sessions found for this project yet.${o.reset} `:`# [${r}] recent context -No previous sessions found for this project yet.`;let y=S.slice(0,s.sessionCount),R=h,n=[];if(e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push(`# [${r}] recent context`),n.push("")),R.length>0){e?n.push(`${o.dim}Legend: \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u2696\uFE0F decision${o.reset}`):n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u2696\uFE0F decision"),n.push(""),e?(n.push(`${o.bright}\u{1F4A1} Column Key${o.reset}`),n.push(`${o.dim} Read: Tokens to read this observation (cost to learn it now)${o.reset}`),n.push(`${o.dim} Work: Tokens spent on work that produced this record (\u{1F50D} research, \u{1F6E0}\uFE0F building, \u2696\uFE0F deciding)${o.reset}`)):(n.push("\u{1F4A1} **Column Key**:"),n.push("- **Read**: Tokens to read this observation (cost to learn it now)"),n.push("- **Work**: Tokens spent on work that produced this record (\u{1F50D} research, \u{1F6E0}\uFE0F building, \u2696\uFE0F deciding)")),n.push(""),e?(n.push(`${o.dim}\u{1F4A1} Context Index: This semantic index (titles, types, files, tokens) is usually sufficient to understand past work.${o.reset}`),n.push(""),n.push(`${o.dim}When you need implementation details, rationale, or debugging context:${o.reset}`),n.push(`${o.dim} - Use the mem-search skill to fetch full observations on-demand${o.reset}`),n.push(`${o.dim} - Critical types (\u{1F534} bugfix, \u2696\uFE0F decision) often need detailed fetching${o.reset}`),n.push(`${o.dim} - Trust this index over re-reading code for past decisions and learnings${o.reset}`)):(n.push("\u{1F4A1} **Context Index:** This semantic index (titles, types, files, tokens) is usually sufficient to understand past work."),n.push(""),n.push("When you need implementation details, rationale, or debugging context:"),n.push("- Use the mem-search skill to fetch full observations on-demand"),n.push("- Critical types (\u{1F534} bugfix, \u2696\uFE0F decision) often need detailed fetching"),n.push("- Trust this index over re-reading code for past decisions and learnings")),n.push("");let m=h.length,p=h.reduce((a,E)=>{let T=(E.title?.length||0)+(E.subtitle?.length||0)+(E.narrative?.length||0)+JSON.stringify(E.facts||[]).length;return a+Math.ceil(T/ue)},0),N=h.reduce((a,E)=>a+(E.discovery_tokens||0),0),L=N-p,D=N>0?Math.round(L/N*100):0;if(e){if(n.push(`${o.bright}${o.cyan}\u{1F4CA} Context Economics${o.reset}`),s.showReadTokens&&n.push(`${o.dim} Loading: ${m} observations (${p.toLocaleString()} tokens to read)${o.reset}`),s.showWorkTokens&&n.push(`${o.dim} Work investment: ${N.toLocaleString()} tokens spent on research, building, and decisions${o.reset}`),N>0&&(s.showSavingsAmount||s.showSavingsPercent)){let a=" Your savings: ";s.showSavingsAmount&&s.showSavingsPercent?a+=`${L.toLocaleString()} tokens (${D}% reduction from reuse)`:s.showSavingsAmount?a+=`${L.toLocaleString()} tokens`:a+=`${D}% reduction from reuse`,n.push(`${o.green}${a}${o.reset}`)}n.push("")}else{if(n.push("\u{1F4CA} **Context Economics**:"),s.showReadTokens&&n.push(`- Loading: ${m} observations (${p.toLocaleString()} tokens to read)`),s.showWorkTokens&&n.push(`- Work investment: ${N.toLocaleString()} tokens spent on research, building, and decisions`),N>0&&(s.showSavingsAmount||s.showSavingsPercent)){let a="- Your savings: ";s.showSavingsAmount&&s.showSavingsPercent?a+=`${L.toLocaleString()} tokens (${D}% reduction from reuse)`:s.showSavingsAmount?a+=`${L.toLocaleString()} tokens`:a+=`${D}% reduction from reuse`,n.push(a)}n.push("")}let me=S[0]?.id,Ee=y.map((a,E)=>{let T=E===0?null:S[E+1];return{...a,displayEpoch:T?T.created_at_epoch:a.created_at_epoch,displayTime:T?T.created_at:a.created_at,shouldShowLink:a.id!==me}}),Te=new Set(h.slice(0,s.fullObservationCount).map(a=>a.id)),ee=[...R.map(a=>({type:"observation",data:a})),...Ee.map(a=>({type:"summary",data:a}))];ee.sort((a,E)=>{let T=a.type==="observation"?a.data.created_at_epoch:a.data.displayEpoch,A=E.type==="observation"?E.data.created_at_epoch:E.data.displayEpoch;return T-A});let w=new Map;for(let a of ee){let E=a.type==="observation"?a.data.created_at:a.data.displayTime,T=Fe(E);w.has(T)||w.set(T,[]),w.get(T).push(a)}let he=Array.from(w.entries()).sort((a,E)=>{let T=new Date(a[0]).getTime(),A=new Date(E[0]).getTime();return T-A});for(let[a,E]of he){e?(n.push(`${o.bright}${o.cyan}${a}${o.reset}`),n.push("")):(n.push(`### ${a}`),n.push(""));let T=null,A="",C=!1;for(let j of E)if(j.type==="summary"){C&&(n.push(""),C=!1,T=null,A="");let _=j.data,M=`${_.request||"Session started"} (${Ue(_.displayTime)})`,I=_.shouldShowLink?`claude-mem://session-summary/${_.id}`:"";if(e){let O=I?`${o.dim}[${I}]${o.reset}`:"";n.push(`\u{1F3AF} ${o.yellow}#S${_.id}${o.reset} ${M} ${O}`)}else{let O=I?` [\u2192](${I})`:"";n.push(`**\u{1F3AF} #S${_.id}** ${M}${O}`)}n.push("")}else{let _=j.data,M=le(_.files_modified),I=M.length>0?Xe(M[0],t):"General";I!==T&&(C&&n.push(""),e?n.push(`${o.dim}${I}${o.reset}`):n.push(`**${I}**`),e||(n.push("| ID | Time | T | Title | Read | Work |"),n.push("|----|------|---|-------|------|------|")),T=I,C=!0,A="");let O=we(_.created_at),F=_.title||"Untitled",X=ae[_.type]||"\u2022",ge=(_.title?.length||0)+(_.subtitle?.length||0)+(_.narrative?.length||0)+JSON.stringify(_.facts||[]).length,k=Math.ceil(ge/ue),$=_.discovery_tokens||0,H=de[_.type]||"\u{1F50D}",te=$>0?`${H} ${$.toLocaleString()}`:"-",G=O!==A,re=G?O:"";if(A=O,Te.has(_.id)){let x=s.fullObservationField==="narrative"?_.narrative:_.facts?le(_.facts).join(` -`):null;if(e){let Y=G?`${o.dim}${O}${o.reset}`:" ".repeat(O.length),V=k>0?`${o.dim}(~${k}t)${o.reset}`:"",Se=$>0?`${o.dim}(${H} ${$.toLocaleString()}t)${o.reset}`:"";n.push(` ${o.dim}#${_.id}${o.reset} ${Y} ${X} ${o.bright}${F}${o.reset}`),x&&n.push(` ${o.dim}${x}${o.reset}`),n.push(` ${V} ${Se}`),n.push("")}else C&&(n.push(""),C=!1),n.push(`**#${_.id}** ${re||"\u2033"} ${X} **${F}**`),x&&(n.push(""),n.push(x),n.push("")),n.push(`Read: ~${k}, Work: ${te}`),n.push(""),T=null}else if(e){let x=G?`${o.dim}${O}${o.reset}`:" ".repeat(O.length),Y=k>0?`${o.dim}(~${k}t)${o.reset}`:"",V=$>0?`${o.dim}(${H} ${$.toLocaleString()}t)${o.reset}`:"";n.push(` ${o.dim}#${_.id}${o.reset} ${x} ${X} ${F} ${Y} ${V}`)}else n.push(`| #${_.id} | ${re||"\u2033"} | ${X} | ${F} | ~${k} | ${te} |`)}C&&n.push("")}let b=S[0],se=h[0];if(s.showLastSummary&&b&&(b.investigated||b.learned||b.completed||b.next_steps)&&(!se||b.created_at_epoch>se.created_at_epoch)&&(n.push(...W("Investigated",b.investigated,o.blue,e)),n.push(...W("Learned",b.learned,o.yellow,e)),n.push(...W("Completed",b.completed,o.green,e)),n.push(...W("Next Steps",b.next_steps,o.magenta,e))),s.showLastMessage&&b){let a=b.last_assistant_message;a&&(n.push(""),e?(n.push(`${o.bright}${o.magenta}\u{1F4AC} Last Message from Previous Session${o.reset}`),n.push(`${o.dim}${a}${o.reset}`)):(n.push("**\u{1F4AC} Last Message from Previous Session**"),n.push(""),n.push(a)),n.push(""))}if(N>0&&L>0){let a=Math.round(N/1e3);n.push(""),e?n.push(`${o.dim}\u{1F4B0} Access ${a}k tokens of past research & decisions for just ${p.toLocaleString()}t. Use the mem-search skill to access memories by ID instead of re-reading files.${o.reset}`):n.push(`\u{1F4B0} Access ${a}k tokens of past research & decisions for just ${p.toLocaleString()}t. Use the mem-search skill to access memories by ID instead of re-reading files.`)}}return i.close(),n.join(` -`).trimEnd()}var Pe=process.argv.includes("--colors");if(Z.isTTY||Pe)_e(void 0,!0).then(u=>{console.log(u),process.exit(0)});else{let u="";Z.on("data",e=>u+=e),Z.on("end",async()=>{let e=u.trim()?JSON.parse(u):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:await _e(e,!1)}};console.log(JSON.stringify(t)),process.exit(0)})} +No previous sessions found for this project yet.`;let A=S.slice(0,s.sessionCount),R=h,n=[];if(e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push(`# [${r}] recent context`),n.push("")),R.length>0){e?n.push(`${o.dim}Legend: \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u2696\uFE0F decision${o.reset}`):n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u2696\uFE0F decision"),n.push(""),e?(n.push(`${o.bright}\u{1F4A1} Column Key${o.reset}`),n.push(`${o.dim} Read: Tokens to read this observation (cost to learn it now)${o.reset}`),n.push(`${o.dim} Work: Tokens spent on work that produced this record (\u{1F50D} research, \u{1F6E0}\uFE0F building, \u2696\uFE0F deciding)${o.reset}`)):(n.push("\u{1F4A1} **Column Key**:"),n.push("- **Read**: Tokens to read this observation (cost to learn it now)"),n.push("- **Work**: Tokens spent on work that produced this record (\u{1F50D} research, \u{1F6E0}\uFE0F building, \u2696\uFE0F deciding)")),n.push(""),e?(n.push(`${o.dim}\u{1F4A1} Context Index: This semantic index (titles, types, files, tokens) is usually sufficient to understand past work.${o.reset}`),n.push(""),n.push(`${o.dim}When you need implementation details, rationale, or debugging context:${o.reset}`),n.push(`${o.dim} - Use the mem-search skill to fetch full observations on-demand${o.reset}`),n.push(`${o.dim} - Critical types (\u{1F534} bugfix, \u2696\uFE0F decision) often need detailed fetching${o.reset}`),n.push(`${o.dim} - Trust this index over re-reading code for past decisions and learnings${o.reset}`)):(n.push("\u{1F4A1} **Context Index:** This semantic index (titles, types, files, tokens) is usually sufficient to understand past work."),n.push(""),n.push("When you need implementation details, rationale, or debugging context:"),n.push("- Use the mem-search skill to fetch full observations on-demand"),n.push("- Critical types (\u{1F534} bugfix, \u2696\uFE0F decision) often need detailed fetching"),n.push("- Trust this index over re-reading code for past decisions and learnings")),n.push("");let m=h.length,p=h.reduce((a,E)=>{let T=(E.title?.length||0)+(E.subtitle?.length||0)+(E.narrative?.length||0)+JSON.stringify(E.facts||[]).length;return a+Math.ceil(T/_e)},0),N=h.reduce((a,E)=>a+(E.discovery_tokens||0),0),L=N-p,$=N>0?Math.round(L/N*100):0,ee=s.showReadTokens||s.showWorkTokens||s.showSavingsAmount||s.showSavingsPercent;if(ee)if(e){if(n.push(`${o.bright}${o.cyan}\u{1F4CA} Context Economics${o.reset}`),n.push(`${o.dim} Loading: ${m} observations (${p.toLocaleString()} tokens to read)${o.reset}`),n.push(`${o.dim} Work investment: ${N.toLocaleString()} tokens spent on research, building, and decisions${o.reset}`),N>0&&(s.showSavingsAmount||s.showSavingsPercent)){let a=" Your savings: ";s.showSavingsAmount&&s.showSavingsPercent?a+=`${L.toLocaleString()} tokens (${$}% reduction from reuse)`:s.showSavingsAmount?a+=`${L.toLocaleString()} tokens`:a+=`${$}% reduction from reuse`,n.push(`${o.green}${a}${o.reset}`)}n.push("")}else{if(n.push("\u{1F4CA} **Context Economics**:"),n.push(`- Loading: ${m} observations (${p.toLocaleString()} tokens to read)`),n.push(`- Work investment: ${N.toLocaleString()} tokens spent on research, building, and decisions`),N>0&&(s.showSavingsAmount||s.showSavingsPercent)){let a="- Your savings: ";s.showSavingsAmount&&s.showSavingsPercent?a+=`${L.toLocaleString()} tokens (${$}% reduction from reuse)`:s.showSavingsAmount?a+=`${L.toLocaleString()} tokens`:a+=`${$}% reduction from reuse`,n.push(a)}n.push("")}let Te=S[0]?.id,he=A.map((a,E)=>{let T=E===0?null:S[E+1];return{...a,displayEpoch:T?T.created_at_epoch:a.created_at_epoch,displayTime:T?T.created_at:a.created_at,shouldShowLink:a.id!==Te}}),ge=new Set(h.slice(0,s.fullObservationCount).map(a=>a.id)),se=[...R.map(a=>({type:"observation",data:a})),...he.map(a=>({type:"summary",data:a}))];se.sort((a,E)=>{let T=a.type==="observation"?a.data.created_at_epoch:a.data.displayEpoch,C=E.type==="observation"?E.data.created_at_epoch:E.data.displayEpoch;return T-C});let F=new Map;for(let a of se){let E=a.type==="observation"?a.data.created_at:a.data.displayTime,T=Xe(E);F.has(T)||F.set(T,[]),F.get(T).push(a)}let Se=Array.from(F.entries()).sort((a,E)=>{let T=new Date(a[0]).getTime(),C=new Date(E[0]).getTime();return T-C});for(let[a,E]of Se){e?(n.push(`${o.bright}${o.cyan}${a}${o.reset}`),n.push("")):(n.push(`### ${a}`),n.push(""));let T=null,C="",k=!1;for(let G of E)if(G.type==="summary"){k&&(n.push(""),k=!1,T=null,C="");let _=G.data,w=`${_.request||"Session started"} (${Ue(_.displayTime)})`,v=_.shouldShowLink?`claude-mem://session-summary/${_.id}`:"";if(e){let O=v?`${o.dim}[${v}]${o.reset}`:"";n.push(`\u{1F3AF} ${o.yellow}#S${_.id}${o.reset} ${w} ${O}`)}else{let O=v?` [\u2192](${v})`:"";n.push(`**\u{1F3AF} #S${_.id}** ${w}${O}`)}n.push("")}else{let _=G.data,w=me(_.files_modified),v=w.length>0?Pe(w[0],t):"General";v!==T&&(k&&n.push(""),e?n.push(`${o.dim}${v}${o.reset}`):n.push(`**${v}**`),e||(n.push("| ID | Time | T | Title | Read | Work |"),n.push("|----|------|---|-------|------|------|")),T=v,k=!0,C="");let O=Fe(_.created_at),X=_.title||"Untitled",P=ce[_.type]||"\u2022",be=(_.title?.length||0)+(_.subtitle?.length||0)+(_.narrative?.length||0)+JSON.stringify(_.facts||[]).length,x=Math.ceil(be/_e),M=_.discovery_tokens||0,Y=pe[_.type]||"\u{1F50D}",re=M>0?`${Y} ${M.toLocaleString()}`:"-",V=O!==C,ne=V?O:"";if(C=O,ge.has(_.id)){let D=s.fullObservationField==="narrative"?_.narrative:_.facts?me(_.facts).join(` +`):null;if(e){let I=V?`${o.dim}${O}${o.reset}`:" ".repeat(O.length),B=s.showReadTokens&&x>0?`${o.dim}(~${x}t)${o.reset}`:"",oe=s.showWorkTokens&&M>0?`${o.dim}(${Y} ${M.toLocaleString()}t)${o.reset}`:"";n.push(` ${o.dim}#${_.id}${o.reset} ${I} ${P} ${o.bright}${X}${o.reset}`),D&&n.push(` ${o.dim}${D}${o.reset}`),(B||oe)&&n.push(` ${B} ${oe}`),n.push("")}else{k&&(n.push(""),k=!1),n.push(`**#${_.id}** ${ne||"\u2033"} ${P} **${X}**`),D&&(n.push(""),n.push(D),n.push(""));let I=[];s.showReadTokens&&I.push(`Read: ~${x}`),s.showWorkTokens&&I.push(`Work: ${re}`),I.length>0&&n.push(I.join(", ")),n.push(""),T=null}}else if(e){let D=V?`${o.dim}${O}${o.reset}`:" ".repeat(O.length),I=s.showReadTokens&&x>0?`${o.dim}(~${x}t)${o.reset}`:"",B=s.showWorkTokens&&M>0?`${o.dim}(${Y} ${M.toLocaleString()}t)${o.reset}`:"";n.push(` ${o.dim}#${_.id}${o.reset} ${D} ${P} ${X} ${I} ${B}`)}else{let D=s.showReadTokens?`~${x}`:"",I=s.showWorkTokens?re:"";n.push(`| #${_.id} | ${ne||"\u2033"} | ${P} | ${X} | ${D} | ${I} |`)}}k&&n.push("")}let b=S[0],te=h[0];if(s.showLastSummary&&b&&(b.investigated||b.learned||b.completed||b.next_steps)&&(!te||b.created_at_epoch>te.created_at_epoch)&&(n.push(...H("Investigated",b.investigated,o.blue,e)),n.push(...H("Learned",b.learned,o.yellow,e)),n.push(...H("Completed",b.completed,o.green,e)),n.push(...H("Next Steps",b.next_steps,o.magenta,e))),s.showLastMessage&&b){let a=b.last_assistant_message;a&&(n.push(""),e?(n.push(`${o.bright}${o.magenta}\u{1F4AC} Last Message from Previous Session${o.reset}`),n.push(`${o.dim}${a}${o.reset}`)):(n.push("**\u{1F4AC} Last Message from Previous Session**"),n.push(""),n.push(a)),n.push(""))}if(ee&&N>0&&L>0){let a=Math.round(N/1e3);n.push(""),e?n.push(`${o.dim}\u{1F4B0} Access ${a}k tokens of past research & decisions for just ${p.toLocaleString()}t. Use the mem-search skill to access memories by ID instead of re-reading files.${o.reset}`):n.push(`\u{1F4B0} Access ${a}k tokens of past research & decisions for just ${p.toLocaleString()}t. Use the mem-search skill to access memories by ID instead of re-reading files.`)}}return i.close(),n.join(` +`).trimEnd()}var Be=process.argv.includes("--colors");if(Z.isTTY||Be)Ee(void 0,!0).then(u=>{console.log(u),process.exit(0)});else{let u="";Z.on("data",e=>u+=e),Z.on("end",async()=>{let e=u.trim()?JSON.parse(u):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:await Ee(e,!1)}};console.log(JSON.stringify(t)),process.exit(0)})} diff --git a/src/hooks/context-hook.ts b/src/hooks/context-hook.ts index b83b51b6..35c2b1ee 100644 --- a/src/hooks/context-hook.ts +++ b/src/hooks/context-hook.ts @@ -98,7 +98,7 @@ function loadContextConfig(): ContextConfig { showLastMessage: env.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE === 'true', }; } catch (error) { - logger.warn('CONTEXT', 'Failed to load context settings, using defaults', {}, error as Error); + logger.warn('HOOK', 'Failed to load context settings, using defaults', {}, error as Error); return defaults; } } @@ -370,47 +370,44 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false ? Math.round((savings / totalDiscoveryTokens) * 100) : 0; - // Display Context Economics section - if (useColors) { - output.push(`${colors.bright}${colors.cyan}📊 Context Economics${colors.reset}`); - if (config.showReadTokens) { + // Display Context Economics section only if at least one token setting is enabled + const showContextEconomics = config.showReadTokens || config.showWorkTokens || + config.showSavingsAmount || config.showSavingsPercent; + + if (showContextEconomics) { + if (useColors) { + output.push(`${colors.bright}${colors.cyan}📊 Context Economics${colors.reset}`); output.push(`${colors.dim} Loading: ${totalObservations} observations (${totalReadTokens.toLocaleString()} tokens to read)${colors.reset}`); - } - if (config.showWorkTokens) { output.push(`${colors.dim} Work investment: ${totalDiscoveryTokens.toLocaleString()} tokens spent on research, building, and decisions${colors.reset}`); - } - if (totalDiscoveryTokens > 0 && (config.showSavingsAmount || config.showSavingsPercent)) { - let savingsLine = ' Your savings: '; - if (config.showSavingsAmount && config.showSavingsPercent) { - savingsLine += `${savings.toLocaleString()} tokens (${savingsPercent}% reduction from reuse)`; - } else if (config.showSavingsAmount) { - savingsLine += `${savings.toLocaleString()} tokens`; - } else { - savingsLine += `${savingsPercent}% reduction from reuse`; + if (totalDiscoveryTokens > 0 && (config.showSavingsAmount || config.showSavingsPercent)) { + let savingsLine = ' Your savings: '; + if (config.showSavingsAmount && config.showSavingsPercent) { + savingsLine += `${savings.toLocaleString()} tokens (${savingsPercent}% reduction from reuse)`; + } else if (config.showSavingsAmount) { + savingsLine += `${savings.toLocaleString()} tokens`; + } else { + savingsLine += `${savingsPercent}% reduction from reuse`; + } + output.push(`${colors.green}${savingsLine}${colors.reset}`); } - output.push(`${colors.green}${savingsLine}${colors.reset}`); - } - output.push(''); - } else { - output.push(`📊 **Context Economics**:`); - if (config.showReadTokens) { + output.push(''); + } else { + output.push(`📊 **Context Economics**:`); output.push(`- Loading: ${totalObservations} observations (${totalReadTokens.toLocaleString()} tokens to read)`); - } - if (config.showWorkTokens) { output.push(`- Work investment: ${totalDiscoveryTokens.toLocaleString()} tokens spent on research, building, and decisions`); - } - if (totalDiscoveryTokens > 0 && (config.showSavingsAmount || config.showSavingsPercent)) { - let savingsLine = '- Your savings: '; - if (config.showSavingsAmount && config.showSavingsPercent) { - savingsLine += `${savings.toLocaleString()} tokens (${savingsPercent}% reduction from reuse)`; - } else if (config.showSavingsAmount) { - savingsLine += `${savings.toLocaleString()} tokens`; - } else { - savingsLine += `${savingsPercent}% reduction from reuse`; + if (totalDiscoveryTokens > 0 && (config.showSavingsAmount || config.showSavingsPercent)) { + let savingsLine = '- Your savings: '; + if (config.showSavingsAmount && config.showSavingsPercent) { + savingsLine += `${savings.toLocaleString()} tokens (${savingsPercent}% reduction from reuse)`; + } else if (config.showSavingsAmount) { + savingsLine += `${savings.toLocaleString()} tokens`; + } else { + savingsLine += `${savingsPercent}% reduction from reuse`; + } + output.push(savingsLine); } - output.push(savingsLine); + output.push(''); } - output.push(''); } // Prepare summaries for timeline display @@ -584,14 +581,16 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false if (useColors) { const timePart = showTime ? `${colors.dim}${time}${colors.reset}` : ' '.repeat(time.length); - const readPart = readTokens > 0 ? `${colors.dim}(~${readTokens}t)${colors.reset}` : ''; - const discoveryPart = discoveryTokens > 0 ? `${colors.dim}(${workEmoji} ${discoveryTokens.toLocaleString()}t)${colors.reset}` : ''; + const readPart = (config.showReadTokens && readTokens > 0) ? `${colors.dim}(~${readTokens}t)${colors.reset}` : ''; + const discoveryPart = (config.showWorkTokens && discoveryTokens > 0) ? `${colors.dim}(${workEmoji} ${discoveryTokens.toLocaleString()}t)${colors.reset}` : ''; output.push(` ${colors.dim}#${obs.id}${colors.reset} ${timePart} ${icon} ${colors.bright}${title}${colors.reset}`); if (detailField) { output.push(` ${colors.dim}${detailField}${colors.reset}`); } - output.push(` ${readPart} ${discoveryPart}`); + if (readPart || discoveryPart) { + output.push(` ${readPart} ${discoveryPart}`); + } output.push(''); } else { // Close table for full observation @@ -606,7 +605,16 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false output.push(detailField); output.push(''); } - output.push(`Read: ~${readTokens}, Work: ${discoveryDisplay}`); + const tokenParts: string[] = []; + if (config.showReadTokens) { + tokenParts.push(`Read: ~${readTokens}`); + } + if (config.showWorkTokens) { + tokenParts.push(`Work: ${discoveryDisplay}`); + } + if (tokenParts.length > 0) { + output.push(tokenParts.join(', ')); + } output.push(''); // Reopen table for next items if in same file @@ -616,11 +624,13 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false // Compact index rendering (existing code) if (useColors) { const timePart = showTime ? `${colors.dim}${time}${colors.reset}` : ' '.repeat(time.length); - const readPart = readTokens > 0 ? `${colors.dim}(~${readTokens}t)${colors.reset}` : ''; - const discoveryPart = discoveryTokens > 0 ? `${colors.dim}(${workEmoji} ${discoveryTokens.toLocaleString()}t)${colors.reset}` : ''; + const readPart = (config.showReadTokens && readTokens > 0) ? `${colors.dim}(~${readTokens}t)${colors.reset}` : ''; + const discoveryPart = (config.showWorkTokens && discoveryTokens > 0) ? `${colors.dim}(${workEmoji} ${discoveryTokens.toLocaleString()}t)${colors.reset}` : ''; output.push(` ${colors.dim}#${obs.id}${colors.reset} ${timePart} ${icon} ${title} ${readPart} ${discoveryPart}`); } else { - output.push(`| #${obs.id} | ${timeDisplay || '″'} | ${icon} | ${title} | ~${readTokens} | ${discoveryDisplay} |`); + const readCol = config.showReadTokens ? `~${readTokens}` : ''; + const workCol = config.showWorkTokens ? discoveryDisplay : ''; + output.push(`| #${obs.id} | ${timeDisplay || '″'} | ${icon} | ${title} | ${readCol} | ${workCol} |`); } } } @@ -670,8 +680,8 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false } } - // Footer with token savings message - if (totalDiscoveryTokens > 0 && savings > 0) { + // Footer with token savings message (only show if token economics is visible) + if (showContextEconomics && totalDiscoveryTokens > 0 && savings > 0) { const workTokensK = Math.round(totalDiscoveryTokens / 1000); output.push(''); if (useColors) {