22const fs = require ( 'fs' ) ;
33const path = require ( 'path' ) ;
44const childProcess = require ( 'child_process' ) ;
5- const { estimateTokens, countTokensViaAPI } = require ( './lib/tokenizer' ) ;
6- const { classifyRisk, contextHeadroom } = require ( './lib/risk' ) ;
7- const { loadConfig } = require ( './lib/config' ) ;
8- const { appendHistory } = require ( './lib/history' ) ;
5+ const { estimateTokens, countTokensViaAPI } = require ( './lib/tokenizer' ) ;
6+ const { classifyRisk, contextHeadroom } = require ( './lib/risk' ) ;
7+ const { loadConfig } = require ( './lib/config' ) ;
8+ const { appendHistory } = require ( './lib/history' ) ;
9+ const { estimateContextOverhead } = require ( './lib/context' ) ;
910
1011const LINE = '━' . repeat ( 35 ) ;
12+ const SEP = '─' . repeat ( 35 ) ;
13+ const COL = 28 ; // label column width
14+
1115const HELP = `
1216⚡ MIMIR — preflight checker
1317
1418Usage:
1519 /mimir "<task>" Estimate token cost + risk
16- /mimir "<task>" --files f1 f2 Include file content in estimate
20+ /mimir "<task>" --files f1 f2 Include specific files in estimate
1721 /mimir "<task>" --git-diff Include current git diff in estimate
22+ /mimir "<task>" --turns N Include N conversation turns in estimate
1823 /split-task "<task>" Split large task into safer sub-tasks
1924
2025Risk levels: LOW ✅ MEDIUM ⚠️ HIGH 🔴 CRITICAL 🚨
2126
27+ Token sources always included:
28+ - System overhead (~3k) — system prompt + command file + hooks
29+ - CLAUDE.md files — user (~/.claude/) and project (.claude/)
30+
2231Tip: run /mimir before any task that reads many files or touches large codebases.
2332` . trimStart ( ) ;
2433
2534function parseArgs ( argv ) {
26- const gitDiffIdx = argv . indexOf ( '--git-diff' ) ;
27- const filesIdx = argv . indexOf ( '--files' ) ;
28- const baseArgv = argv . filter ( a => a !== '--git-diff' ) ;
29- const fi = baseArgv . indexOf ( '--files' ) ;
30- if ( fi === - 1 ) return { task : baseArgv . join ( ' ' ) . trim ( ) , filePaths : [ ] , useGitDiff : gitDiffIdx !== - 1 } ;
35+ const useGitDiff = argv . includes ( '--git-diff' ) ;
36+ const turnsIdx = argv . indexOf ( '--turns' ) ;
37+ const turns = turnsIdx !== - 1 ? ( parseInt ( argv [ turnsIdx + 1 ] , 10 ) || 0 ) : 0 ;
38+
39+ const clean = [ ] ;
40+ let i = 0 ;
41+ while ( i < argv . length ) {
42+ if ( argv [ i ] === '--git-diff' ) { i ++ ; continue ; }
43+ if ( argv [ i ] === '--turns' ) { i += 2 ; continue ; }
44+ clean . push ( argv [ i ] ) ;
45+ i ++ ;
46+ }
47+
48+ const filesIdx = clean . indexOf ( '--files' ) ;
49+ if ( filesIdx === - 1 ) {
50+ return { task : clean . join ( ' ' ) . trim ( ) , filePaths : [ ] , useGitDiff, turns } ;
51+ }
3152 return {
32- task : baseArgv . slice ( 0 , fi ) . join ( ' ' ) . trim ( ) ,
33- filePaths : baseArgv . slice ( fi + 1 ) ,
34- useGitDiff : gitDiffIdx !== - 1 ,
53+ task : clean . slice ( 0 , filesIdx ) . join ( ' ' ) . trim ( ) ,
54+ filePaths : clean . slice ( filesIdx + 1 ) ,
55+ useGitDiff,
56+ turns,
3557 } ;
3658}
3759
@@ -57,6 +79,11 @@ function fmt(method, n) {
5779 return method === 'exact' ? n . toLocaleString ( ) : `~${ n . toLocaleString ( ) } ` ;
5880}
5981
82+ function row ( label , value ) {
83+ const pad = Math . max ( 1 , COL - label . length ) ;
84+ process . stdout . write ( ` ${ label } ${ ' ' . repeat ( pad ) } ${ value } \n` ) ;
85+ }
86+
6087async function countText ( text , method ) {
6188 if ( method === 'forced-heuristic' ) return { tokens : estimateTokens ( text ) , method : 'heuristic' } ;
6289 const api = await countTokensViaAPI ( text ) ;
@@ -65,71 +92,76 @@ async function countText(text, method) {
6592}
6693
6794async function main ( ) {
68- const { task, filePaths, useGitDiff } = parseArgs ( process . argv . slice ( 2 ) ) ;
95+ const { task, filePaths, useGitDiff, turns } = parseArgs ( process . argv . slice ( 2 ) ) ;
6996
7097 if ( ! task ) {
7198 process . stdout . write ( HELP ) ;
7299 process . exit ( 0 ) ;
73100 }
74101
75- const cfg = loadConfig ( ) ;
76- const taskResult = await countText ( task , 'auto' ) ;
77- const method = taskResult . method ;
102+ const cfg = loadConfig ( ) ;
103+ const taskResult = await countText ( task , 'auto' ) ;
104+ const method = taskResult . method ;
105+ const ctx = estimateContextOverhead ( cfg , { turns, cwd : process . cwd ( ) } ) ;
78106
79- let totalTokens = taskResult . tokens ;
80107 const fileResults = [ ] ;
81-
108+ let fileTotal = 0 ;
82109 for ( const fp of filePaths ) {
83110 const f = readFile ( fp ) ;
84111 if ( f . error ) {
85112 fileResults . push ( { path : fp , tokens : 0 , error : f . error } ) ;
86113 } else {
87114 const r = await countText ( f . content , 'forced-heuristic' ) ;
88115 fileResults . push ( { path : fp , tokens : r . tokens , error : null } ) ;
89- totalTokens += r . tokens ;
116+ fileTotal += r . tokens ;
90117 }
91118 }
92119
93120 let diffTokens = 0 ;
94121 if ( useGitDiff ) {
95122 const diff = getGitDiff ( ) ;
96- if ( diff ) {
97- diffTokens = estimateTokens ( diff ) ;
98- totalTokens += diffTokens ;
99- }
123+ if ( diff ) diffTokens = estimateTokens ( diff ) ;
100124 }
101125
102- const risk = classifyRisk ( totalTokens , cfg , task ) ;
103- const headroom = contextHeadroom ( totalTokens , cfg . contextWindow ) ;
126+ const totalTokens = taskResult . tokens + ctx . total + fileTotal + diffTokens ;
127+ const risk = classifyRisk ( totalTokens , cfg , task ) ;
128+ const headroom = contextHeadroom ( totalTokens , cfg . contextWindow ) ;
129+ const modelLine = cfg . defaultModel ? `${ cfg . defaultModel } (from .mimir.json)` : risk . suggestedModel ;
104130
105131 process . stdout . write ( `\n⚡ MIMIR PREFLIGHT\n` ) ;
106132 process . stdout . write ( `${ LINE } \n` ) ;
107133
108- const hasExtras = filePaths . length > 0 || useGitDiff ;
109-
110- if ( hasExtras ) {
111- const filesTotal = fileResults . reduce ( ( s , f ) => s + f . tokens , 0 ) ;
112- process . stdout . write ( ` Task tokens (${ method } ): ${ fmt ( method , taskResult . tokens ) } \n` ) ;
113- if ( fileResults . length > 0 ) {
114- process . stdout . write ( ` Files tokens: ~${ filesTotal . toLocaleString ( ) } \n` ) ;
115- for ( const f of fileResults ) {
116- const name = path . basename ( f . path ) . padEnd ( 20 ) . slice ( 0 , 20 ) ;
117- if ( f . error ) process . stdout . write ( ` ${ name } ⚠ ${ f . error } \n` ) ;
118- else process . stdout . write ( ` ${ name } ~${ f . tokens . toLocaleString ( ) } \n` ) ;
119- }
120- }
121- if ( useGitDiff ) {
122- process . stdout . write ( ` Git diff tokens: ~${ diffTokens . toLocaleString ( ) } \n` ) ;
134+ // Token sources
135+ row ( `Task (${ method } ):` , fmt ( method , taskResult . tokens ) ) ;
136+ row ( 'System overhead:' , `~${ ctx . systemOverhead . toLocaleString ( ) } (prompt + command + hooks)` ) ;
137+
138+ for ( const md of ctx . claudeMds ) {
139+ const label = `${ md . label } :` ;
140+ if ( md . error ) row ( label , `⚠ ${ md . error } ` ) ;
141+ else row ( label , `~${ md . tokens . toLocaleString ( ) } ` ) ;
142+ }
143+
144+ if ( fileResults . length > 0 ) {
145+ for ( const f of fileResults ) {
146+ const name = path . basename ( f . path ) ;
147+ const label = `${ name } :` ;
148+ if ( f . error ) row ( label , `⚠ ${ f . error } ` ) ;
149+ else row ( label , `~${ f . tokens . toLocaleString ( ) } ` ) ;
123150 }
124- process . stdout . write ( ` Total tokens: ~${ totalTokens . toLocaleString ( ) } \n` ) ;
125- } else {
126- process . stdout . write ( ` Input tokens (${ method } ): ${ fmt ( method , totalTokens ) } \n` ) ;
127151 }
128152
129- const modelLine = cfg . defaultModel
130- ? `${ cfg . defaultModel } (from .mimir.json)`
131- : risk . suggestedModel ;
153+ if ( useGitDiff ) {
154+ row ( 'Git diff:' , `~${ diffTokens . toLocaleString ( ) } ` ) ;
155+ }
156+
157+ if ( turns > 0 ) {
158+ row ( `Conversation (${ turns } turns):` , `~${ ctx . conversationTokens . toLocaleString ( ) } (~800 tok/turn)` ) ;
159+ }
160+
161+ process . stdout . write ( ` ${ SEP } \n` ) ;
162+ row ( 'Total:' , `~${ totalTokens . toLocaleString ( ) } ` ) ;
132163
164+ process . stdout . write ( `\n` ) ;
133165 process . stdout . write ( ` Risk: ${ risk . level } ${ risk . emoji } \n` ) ;
134166 process . stdout . write ( ` Suggested model: ${ modelLine } \n` ) ;
135167 process . stdout . write ( ` Context headroom: ${ headroom } %\n` ) ;
0 commit comments