claude-cli-advanced-starter-pack 1.0.16 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/OVERVIEW.md +5 -1
  2. package/README.md +241 -132
  3. package/bin/gtask.js +53 -0
  4. package/package.json +1 -1
  5. package/src/cli/menu.js +27 -0
  6. package/src/commands/explore-mcp/mcp-registry.js +99 -0
  7. package/src/commands/init.js +309 -80
  8. package/src/commands/install-panel-hook.js +108 -0
  9. package/src/commands/install-scripts.js +232 -0
  10. package/src/commands/install-skill.js +220 -0
  11. package/src/commands/panel.js +297 -0
  12. package/src/commands/setup-wizard.js +4 -3
  13. package/src/commands/test-setup.js +4 -5
  14. package/src/data/releases.json +209 -0
  15. package/src/panel/queue.js +188 -0
  16. package/templates/commands/ask-claude.template.md +118 -0
  17. package/templates/commands/ccasp-panel.template.md +72 -0
  18. package/templates/commands/ccasp-setup.template.md +470 -79
  19. package/templates/commands/create-smoke-test.template.md +186 -0
  20. package/templates/commands/project-impl.template.md +9 -113
  21. package/templates/commands/refactor-check.template.md +112 -0
  22. package/templates/commands/refactor-cleanup.template.md +144 -0
  23. package/templates/commands/refactor-prep.template.md +192 -0
  24. package/templates/docs/AI_ARCHITECTURE_CONSTITUTION.template.md +198 -0
  25. package/templates/docs/DETAILED_GOTCHAS.template.md +347 -0
  26. package/templates/docs/PHASE-DEV-CHECKLIST.template.md +241 -0
  27. package/templates/docs/PROGRESS_JSON_TEMPLATE.json +117 -0
  28. package/templates/docs/background-agent.template.md +264 -0
  29. package/templates/hooks/autonomous-decision-logger.template.js +207 -0
  30. package/templates/hooks/branch-merge-checker.template.js +272 -0
  31. package/templates/hooks/context-injector.template.js +261 -0
  32. package/templates/hooks/git-commit-tracker.template.js +267 -0
  33. package/templates/hooks/happy-mode-detector.template.js +214 -0
  34. package/templates/hooks/happy-title-generator.template.js +260 -0
  35. package/templates/hooks/issue-completion-detector.template.js +205 -0
  36. package/templates/hooks/panel-queue-reader.template.js +83 -0
  37. package/templates/hooks/phase-validation-gates.template.js +307 -0
  38. package/templates/hooks/session-id-generator.template.js +236 -0
  39. package/templates/hooks/token-budget-loader.template.js +234 -0
  40. package/templates/hooks/token-usage-monitor.template.js +193 -0
  41. package/templates/hooks/tool-output-cacher.template.js +219 -0
  42. package/templates/patterns/README.md +129 -0
  43. package/templates/patterns/l1-l2-orchestration.md +189 -0
  44. package/templates/patterns/multi-phase-orchestration.md +258 -0
  45. package/templates/patterns/two-tier-query-pipeline.md +192 -0
  46. package/templates/scripts/README.md +109 -0
  47. package/templates/scripts/analyze-delegation-log.js +299 -0
  48. package/templates/scripts/autonomous-decision-logger.js +277 -0
  49. package/templates/scripts/git-history-analyzer.py +269 -0
  50. package/templates/scripts/phase-validation-gates.js +307 -0
  51. package/templates/scripts/poll-deployment-status.js +260 -0
  52. package/templates/scripts/roadmap-scanner.js +263 -0
  53. package/templates/scripts/validate-deployment.js +293 -0
  54. package/templates/skills/agent-creator/skill.json +18 -0
  55. package/templates/skills/agent-creator/skill.md +335 -0
  56. package/templates/skills/hook-creator/skill.json +18 -0
  57. package/templates/skills/hook-creator/skill.md +318 -0
  58. package/templates/skills/panel/skill.json +18 -0
  59. package/templates/skills/panel/skill.md +90 -0
  60. package/templates/skills/rag-agent-creator/skill.json +18 -0
  61. package/templates/skills/rag-agent-creator/skill.md +307 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Token Budget Loader Hook
3
+ *
4
+ * Pre-calculates daily token budget at session start.
5
+ * Tracks usage across sessions and provides health status alerts.
6
+ * Saves ~5K tokens/session by pre-loading budget state.
7
+ *
8
+ * Event: UserPromptSubmit (runs once per session)
9
+ *
10
+ * Configuration: Reads from .claude/config/hooks-config.json
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ // Default configuration (can be overridden by hooks-config.json)
17
+ const DEFAULT_CONFIG = {
18
+ daily_limit: 200000, // Daily token budget
19
+ warning_percent: 60, // Warn at this percentage
20
+ critical_percent: 80, // Critical warning at this percentage
21
+ reset_hours: 24, // Budget reset cycle in hours
22
+ };
23
+
24
+ // Paths
25
+ const CONFIG_PATH = path.join(process.cwd(), '.claude', 'config', 'hooks-config.json');
26
+ const BUDGET_PATH = path.join(process.cwd(), '.claude', 'config', 'token-budget.json');
27
+ const SESSION_MARKER = path.join(process.cwd(), '.claude', 'config', '.budget-loaded');
28
+
29
+ /**
30
+ * Load configuration with defaults
31
+ */
32
+ function loadConfig() {
33
+ try {
34
+ if (fs.existsSync(CONFIG_PATH)) {
35
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
36
+ return { ...DEFAULT_CONFIG, ...(config.token_budget || {}) };
37
+ }
38
+ } catch (e) {
39
+ // Use defaults on error
40
+ }
41
+ return DEFAULT_CONFIG;
42
+ }
43
+
44
+ /**
45
+ * Load budget state
46
+ */
47
+ function loadBudget() {
48
+ try {
49
+ if (fs.existsSync(BUDGET_PATH)) {
50
+ return JSON.parse(fs.readFileSync(BUDGET_PATH, 'utf8'));
51
+ }
52
+ } catch (e) {
53
+ // Return fresh budget
54
+ }
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * Save budget state
60
+ */
61
+ function saveBudget(budget) {
62
+ try {
63
+ const dir = path.dirname(BUDGET_PATH);
64
+ if (!fs.existsSync(dir)) {
65
+ fs.mkdirSync(dir, { recursive: true });
66
+ }
67
+ fs.writeFileSync(BUDGET_PATH, JSON.stringify(budget, null, 2), 'utf8');
68
+ } catch (e) {
69
+ console.error(`[token-budget-loader] Failed to save budget: ${e.message}`);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Check if we've already loaded budget this session
75
+ */
76
+ function hasLoadedThisSession() {
77
+ try {
78
+ if (fs.existsSync(SESSION_MARKER)) {
79
+ const content = fs.readFileSync(SESSION_MARKER, 'utf8');
80
+ const timestamp = parseInt(content, 10);
81
+ // Session valid for 4 hours
82
+ if (Date.now() - timestamp < 4 * 60 * 60 * 1000) {
83
+ return true;
84
+ }
85
+ }
86
+ } catch (e) {
87
+ // Continue with loading
88
+ }
89
+ return false;
90
+ }
91
+
92
+ /**
93
+ * Mark session as loaded
94
+ */
95
+ function markSessionLoaded() {
96
+ try {
97
+ const dir = path.dirname(SESSION_MARKER);
98
+ if (!fs.existsSync(dir)) {
99
+ fs.mkdirSync(dir, { recursive: true });
100
+ }
101
+ fs.writeFileSync(SESSION_MARKER, Date.now().toString(), 'utf8');
102
+ } catch (e) {
103
+ // Silent failure
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Check if budget should reset (new day)
109
+ */
110
+ function shouldResetBudget(budget, config) {
111
+ if (!budget || !budget.reset_at) {
112
+ return true;
113
+ }
114
+
115
+ const resetAt = new Date(budget.reset_at);
116
+ const now = new Date();
117
+
118
+ return now >= resetAt;
119
+ }
120
+
121
+ /**
122
+ * Calculate next reset time
123
+ */
124
+ function getNextResetTime(config) {
125
+ const now = new Date();
126
+ const resetTime = new Date(now);
127
+
128
+ // Reset at midnight UTC by default
129
+ resetTime.setUTCHours(0, 0, 0, 0);
130
+ resetTime.setTime(resetTime.getTime() + config.reset_hours * 60 * 60 * 1000);
131
+
132
+ // If we've passed today's reset, move to next cycle
133
+ while (resetTime <= now) {
134
+ resetTime.setTime(resetTime.getTime() + config.reset_hours * 60 * 60 * 1000);
135
+ }
136
+
137
+ return resetTime.toISOString();
138
+ }
139
+
140
+ /**
141
+ * Estimate tokens from characters (rough approximation)
142
+ * Claude uses ~4 chars per token on average
143
+ */
144
+ function estimateTokens(chars) {
145
+ return Math.ceil(chars / 4);
146
+ }
147
+
148
+ /**
149
+ * Get budget health status
150
+ */
151
+ function getBudgetHealth(budget, config) {
152
+ const percentUsed = (budget.used / budget.limit) * 100;
153
+
154
+ if (percentUsed >= config.critical_percent) {
155
+ return {
156
+ status: 'critical',
157
+ message: `CRITICAL: ${percentUsed.toFixed(1)}% of daily budget used`,
158
+ color: 'red',
159
+ };
160
+ } else if (percentUsed >= config.warning_percent) {
161
+ return {
162
+ status: 'warning',
163
+ message: `WARNING: ${percentUsed.toFixed(1)}% of daily budget used`,
164
+ color: 'yellow',
165
+ };
166
+ } else {
167
+ return {
168
+ status: 'healthy',
169
+ message: `Budget healthy: ${percentUsed.toFixed(1)}% used`,
170
+ color: 'green',
171
+ };
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Main hook handler
177
+ */
178
+ module.exports = async function tokenBudgetLoader(context) {
179
+ // Always continue - never block
180
+ const approve = () => ({ continue: true });
181
+
182
+ try {
183
+ // Only run once per session
184
+ if (hasLoadedThisSession()) {
185
+ return approve();
186
+ }
187
+
188
+ // Mark this session as loaded
189
+ markSessionLoaded();
190
+
191
+ const config = loadConfig();
192
+ let budget = loadBudget();
193
+
194
+ // Check if we need to reset the budget
195
+ if (shouldResetBudget(budget, config)) {
196
+ budget = {
197
+ limit: config.daily_limit,
198
+ used: 0,
199
+ reset_at: getNextResetTime(config),
200
+ sessions: [],
201
+ created_at: new Date().toISOString(),
202
+ };
203
+ saveBudget(budget);
204
+ console.log(`[token-budget-loader] Budget reset. Daily limit: ${config.daily_limit.toLocaleString()} tokens`);
205
+ }
206
+
207
+ // Calculate remaining budget
208
+ const remaining = budget.limit - budget.used;
209
+ const health = getBudgetHealth(budget, config);
210
+
211
+ // Log budget status
212
+ console.log(`[token-budget-loader] ${health.message}`);
213
+ console.log(`[token-budget-loader] Remaining: ${remaining.toLocaleString()} tokens`);
214
+ console.log(`[token-budget-loader] Resets at: ${budget.reset_at}`);
215
+
216
+ // Record this session start
217
+ budget.sessions.push({
218
+ started_at: new Date().toISOString(),
219
+ initial_remaining: remaining,
220
+ });
221
+
222
+ // Keep only last 10 sessions
223
+ if (budget.sessions.length > 10) {
224
+ budget.sessions = budget.sessions.slice(-10);
225
+ }
226
+
227
+ saveBudget(budget);
228
+
229
+ return approve();
230
+ } catch (error) {
231
+ console.error(`[token-budget-loader] Error: ${error.message}`);
232
+ return approve();
233
+ }
234
+ };
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Token Usage Monitor Hook
3
+ *
4
+ * Tracks cumulative token usage across the session.
5
+ * Auto-respawn warning at 90% of context window.
6
+ * Provides real-time token consumption feedback.
7
+ *
8
+ * Event: PostToolUse
9
+ *
10
+ * Configuration: Reads from .claude/config/hooks-config.json
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ // Default configuration
17
+ const DEFAULT_CONFIG = {
18
+ context_limit: 200000, // Estimated context window
19
+ warning_threshold: 0.75, // Warn at 75%
20
+ critical_threshold: 0.90, // Critical at 90%
21
+ auto_compact_suggestion: true, // Suggest compaction at critical
22
+ session_timeout_ms: 300000, // 5 minutes
23
+ };
24
+
25
+ // Paths
26
+ const CONFIG_PATH = path.join(process.cwd(), '.claude', 'config', 'hooks-config.json');
27
+ const STATE_PATH = path.join(process.cwd(), '.claude', 'config', 'token-usage-state.json');
28
+
29
+ /**
30
+ * Load configuration
31
+ */
32
+ function loadConfig() {
33
+ try {
34
+ if (fs.existsSync(CONFIG_PATH)) {
35
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
36
+ return { ...DEFAULT_CONFIG, ...(config.token_usage || {}) };
37
+ }
38
+ } catch (e) {
39
+ // Use defaults
40
+ }
41
+ return DEFAULT_CONFIG;
42
+ }
43
+
44
+ /**
45
+ * Load state
46
+ */
47
+ function loadState() {
48
+ try {
49
+ if (fs.existsSync(STATE_PATH)) {
50
+ return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8'));
51
+ }
52
+ } catch (e) {
53
+ // Fresh state
54
+ }
55
+ return {
56
+ session_id: null,
57
+ session_start: null,
58
+ total_input_chars: 0,
59
+ total_output_chars: 0,
60
+ estimated_tokens: 0,
61
+ tool_calls: 0,
62
+ last_activity: null,
63
+ warnings_shown: 0,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Save state
69
+ */
70
+ function saveState(state) {
71
+ try {
72
+ const dir = path.dirname(STATE_PATH);
73
+ if (!fs.existsSync(dir)) {
74
+ fs.mkdirSync(dir, { recursive: true });
75
+ }
76
+ fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), 'utf8');
77
+ } catch (e) {
78
+ // Silent
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Estimate tokens from text (rough approximation)
84
+ */
85
+ function estimateTokens(text) {
86
+ if (!text) return 0;
87
+ // Average ~4 characters per token for English text
88
+ return Math.ceil(text.length / 4);
89
+ }
90
+
91
+ /**
92
+ * Generate session ID
93
+ */
94
+ function generateSessionId() {
95
+ return Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
96
+ }
97
+
98
+ /**
99
+ * Format number with commas
100
+ */
101
+ function formatNumber(num) {
102
+ return num.toLocaleString();
103
+ }
104
+
105
+ /**
106
+ * Main hook handler
107
+ */
108
+ module.exports = async function tokenUsageMonitor(context) {
109
+ const approve = () => ({ continue: true });
110
+
111
+ try {
112
+ const config = loadConfig();
113
+ let state = loadState();
114
+ const now = Date.now();
115
+
116
+ // Check for new session
117
+ if (!state.session_start || (now - state.last_activity) > config.session_timeout_ms) {
118
+ state = {
119
+ session_id: generateSessionId(),
120
+ session_start: now,
121
+ total_input_chars: 0,
122
+ total_output_chars: 0,
123
+ estimated_tokens: 0,
124
+ tool_calls: 0,
125
+ last_activity: now,
126
+ warnings_shown: 0,
127
+ };
128
+ }
129
+
130
+ // Parse hook input
131
+ let inputChars = 0;
132
+ let outputChars = 0;
133
+
134
+ try {
135
+ const input = JSON.parse(process.env.CLAUDE_HOOK_INPUT || '{}');
136
+
137
+ // Count input characters
138
+ if (input.tool_input) {
139
+ const inputStr = typeof input.tool_input === 'string'
140
+ ? input.tool_input
141
+ : JSON.stringify(input.tool_input);
142
+ inputChars = inputStr.length;
143
+ }
144
+
145
+ // Count output characters
146
+ if (input.tool_output) {
147
+ const outputStr = typeof input.tool_output === 'string'
148
+ ? input.tool_output
149
+ : JSON.stringify(input.tool_output);
150
+ outputChars = outputStr.length;
151
+ }
152
+ } catch (e) {
153
+ // Skip counting on error
154
+ }
155
+
156
+ // Update totals
157
+ state.total_input_chars += inputChars;
158
+ state.total_output_chars += outputChars;
159
+ state.tool_calls++;
160
+ state.last_activity = now;
161
+
162
+ // Estimate tokens
163
+ const totalChars = state.total_input_chars + state.total_output_chars;
164
+ state.estimated_tokens = estimateTokens(totalChars);
165
+
166
+ // Calculate usage percentage
167
+ const usagePercent = state.estimated_tokens / config.context_limit;
168
+
169
+ // Check thresholds
170
+ if (usagePercent >= config.critical_threshold) {
171
+ if (state.warnings_shown < 3) { // Don't spam
172
+ console.log(`[token-usage-monitor] CRITICAL: ${(usagePercent * 100).toFixed(1)}% of context used`);
173
+ console.log(`[token-usage-monitor] Estimated tokens: ${formatNumber(state.estimated_tokens)}`);
174
+ if (config.auto_compact_suggestion) {
175
+ console.log('[token-usage-monitor] Consider using /context-audit or starting a new session');
176
+ }
177
+ state.warnings_shown++;
178
+ }
179
+ } else if (usagePercent >= config.warning_threshold) {
180
+ if (state.warnings_shown < 2) {
181
+ console.log(`[token-usage-monitor] WARNING: ${(usagePercent * 100).toFixed(1)}% of context used`);
182
+ state.warnings_shown++;
183
+ }
184
+ }
185
+
186
+ saveState(state);
187
+
188
+ return approve();
189
+ } catch (error) {
190
+ console.error(`[token-usage-monitor] Error: ${error.message}`);
191
+ return approve();
192
+ }
193
+ };
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Tool Output Cacher Hook
3
+ *
4
+ * Caches large tool outputs (>2KB) to reduce context consumption.
5
+ * Monitors cumulative session output and warns at thresholds.
6
+ * Saves ~500 tokens per large output by storing in cache files.
7
+ *
8
+ * Event: PostToolUse
9
+ *
10
+ * Configuration: Reads from .claude/config/hooks-config.json
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const crypto = require('crypto');
16
+
17
+ // Default configuration (can be overridden by hooks-config.json)
18
+ const DEFAULT_CONFIG = {
19
+ threshold_chars: 2048, // Cache outputs larger than this
20
+ warning_threshold: 100000, // Warn when session total exceeds this
21
+ critical_threshold: 150000, // Critical warning at this level
22
+ session_timeout_ms: 300000, // 5 minutes - consider new session
23
+ cacheable_tools: ['Bash', 'Read', 'Glob', 'Grep'],
24
+ bypass_tools: ['Task', 'TaskOutput'],
25
+ };
26
+
27
+ // Paths
28
+ const CONFIG_PATH = path.join(process.cwd(), '.claude', 'config', 'hooks-config.json');
29
+ const CACHE_DIR = path.join(process.cwd(), '.claude', 'cache', 'tool-outputs');
30
+ const STATE_FILE = path.join(process.cwd(), '.claude', 'config', 'tool-output-cacher-state.json');
31
+
32
+ /**
33
+ * Load configuration with defaults
34
+ */
35
+ function loadConfig() {
36
+ try {
37
+ if (fs.existsSync(CONFIG_PATH)) {
38
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
39
+ return { ...DEFAULT_CONFIG, ...(config.output_caching || {}) };
40
+ }
41
+ } catch (e) {
42
+ // Use defaults on error
43
+ }
44
+ return DEFAULT_CONFIG;
45
+ }
46
+
47
+ /**
48
+ * Load session state
49
+ */
50
+ function loadState() {
51
+ try {
52
+ if (fs.existsSync(STATE_FILE)) {
53
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
54
+ }
55
+ } catch (e) {
56
+ // Return fresh state
57
+ }
58
+ return {
59
+ session_id: null,
60
+ session_start: null,
61
+ cumulative_chars: 0,
62
+ cached_count: 0,
63
+ last_activity: null,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Save session state
69
+ */
70
+ function saveState(state) {
71
+ try {
72
+ const dir = path.dirname(STATE_FILE);
73
+ if (!fs.existsSync(dir)) {
74
+ fs.mkdirSync(dir, { recursive: true });
75
+ }
76
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
77
+ } catch (e) {
78
+ // Silent failure - don't block tool execution
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Generate cache key for output
84
+ */
85
+ function generateCacheKey(toolName, output) {
86
+ const hash = crypto.createHash('md5').update(output.slice(0, 500)).digest('hex').slice(0, 8);
87
+ return `${toolName.toLowerCase()}-${Date.now()}-${hash}`;
88
+ }
89
+
90
+ /**
91
+ * Cache output to file
92
+ */
93
+ function cacheOutput(key, output, summary) {
94
+ try {
95
+ if (!fs.existsSync(CACHE_DIR)) {
96
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
97
+ }
98
+
99
+ const cachePath = path.join(CACHE_DIR, `${key}.txt`);
100
+ fs.writeFileSync(cachePath, output, 'utf8');
101
+
102
+ return {
103
+ cached: true,
104
+ path: cachePath,
105
+ summary: summary,
106
+ };
107
+ } catch (e) {
108
+ return { cached: false, error: e.message };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Generate summary of large output
114
+ */
115
+ function generateSummary(output, maxLines = 15) {
116
+ const lines = output.split('\n');
117
+ const totalLines = lines.length;
118
+ const totalChars = output.length;
119
+
120
+ // Take first N lines as preview
121
+ const preview = lines.slice(0, maxLines).join('\n');
122
+
123
+ return {
124
+ preview: preview,
125
+ total_lines: totalLines,
126
+ total_chars: totalChars,
127
+ truncated: totalLines > maxLines,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Check if tool output should be cached
133
+ */
134
+ function shouldCache(toolName, output, config) {
135
+ // Skip bypass tools
136
+ if (config.bypass_tools.includes(toolName)) {
137
+ return false;
138
+ }
139
+
140
+ // Check if tool is cacheable
141
+ const isCacheable = config.cacheable_tools.some(t =>
142
+ toolName.startsWith(t) || toolName === t
143
+ );
144
+
145
+ if (!isCacheable) {
146
+ return false;
147
+ }
148
+
149
+ // Check size threshold
150
+ return output.length > config.threshold_chars;
151
+ }
152
+
153
+ /**
154
+ * Main hook handler
155
+ */
156
+ module.exports = async function toolOutputCacher(context) {
157
+ // Always approve - this is a PostToolUse hook for monitoring only
158
+ const approve = () => ({ continue: true });
159
+
160
+ try {
161
+ // Parse hook input
162
+ const input = JSON.parse(process.env.CLAUDE_HOOK_INPUT || '{}');
163
+ const { tool_name, tool_input, tool_output } = input;
164
+
165
+ if (!tool_name || !tool_output) {
166
+ return approve();
167
+ }
168
+
169
+ const output = typeof tool_output === 'string' ? tool_output : JSON.stringify(tool_output);
170
+ const config = loadConfig();
171
+
172
+ // Load and update session state
173
+ let state = loadState();
174
+ const now = Date.now();
175
+
176
+ // Check if this is a new session
177
+ if (!state.session_start || (now - state.last_activity) > config.session_timeout_ms) {
178
+ state = {
179
+ session_id: crypto.randomBytes(4).toString('hex'),
180
+ session_start: now,
181
+ cumulative_chars: 0,
182
+ cached_count: 0,
183
+ last_activity: now,
184
+ };
185
+ }
186
+
187
+ // Update cumulative tracking
188
+ state.cumulative_chars += output.length;
189
+ state.last_activity = now;
190
+
191
+ // Check if we should cache this output
192
+ if (shouldCache(tool_name, output, config)) {
193
+ const summary = generateSummary(output);
194
+ const cacheKey = generateCacheKey(tool_name, output);
195
+ const cacheResult = cacheOutput(cacheKey, output, summary);
196
+
197
+ if (cacheResult.cached) {
198
+ state.cached_count++;
199
+ console.log(`[tool-output-cacher] Cached ${tool_name} output (${output.length} chars) -> ${cacheResult.path}`);
200
+ }
201
+ }
202
+
203
+ // Check thresholds and warn
204
+ if (state.cumulative_chars >= config.critical_threshold) {
205
+ console.log(`[tool-output-cacher] CRITICAL: Session at ${state.cumulative_chars} chars. Consider compaction.`);
206
+ } else if (state.cumulative_chars >= config.warning_threshold) {
207
+ console.log(`[tool-output-cacher] WARNING: Session at ${state.cumulative_chars} chars.`);
208
+ }
209
+
210
+ // Save state
211
+ saveState(state);
212
+
213
+ return approve();
214
+ } catch (error) {
215
+ // Always approve even on error
216
+ console.error(`[tool-output-cacher] Error: ${error.message}`);
217
+ return approve();
218
+ }
219
+ };