@kevinbermudezc/context-guard 1.0.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/bin/claude-hook.d.ts +10 -0
  4. package/dist/bin/claude-hook.d.ts.map +1 -0
  5. package/dist/bin/claude-hook.js +95 -0
  6. package/dist/bin/claude-hook.js.map +1 -0
  7. package/dist/bin/context-guard.d.ts +3 -0
  8. package/dist/bin/context-guard.d.ts.map +1 -0
  9. package/dist/bin/context-guard.js +64 -0
  10. package/dist/bin/context-guard.js.map +1 -0
  11. package/dist/src/config.d.ts +3 -0
  12. package/dist/src/config.d.ts.map +1 -0
  13. package/dist/src/config.js +16 -0
  14. package/dist/src/config.js.map +1 -0
  15. package/dist/src/index.d.ts +11 -0
  16. package/dist/src/index.d.ts.map +1 -0
  17. package/dist/src/index.js +108 -0
  18. package/dist/src/index.js.map +1 -0
  19. package/dist/src/skeletonizer.d.ts +8 -0
  20. package/dist/src/skeletonizer.d.ts.map +1 -0
  21. package/dist/src/skeletonizer.js +76 -0
  22. package/dist/src/skeletonizer.js.map +1 -0
  23. package/dist/src/types.d.ts +49 -0
  24. package/dist/src/types.d.ts.map +1 -0
  25. package/dist/src/types.js +2 -0
  26. package/dist/src/types.js.map +1 -0
  27. package/dist/src/worker-model.d.ts +2 -0
  28. package/dist/src/worker-model.d.ts.map +1 -0
  29. package/dist/src/worker-model.js +81 -0
  30. package/dist/src/worker-model.js.map +1 -0
  31. package/dist/tests/claude-hook.test.d.ts +2 -0
  32. package/dist/tests/claude-hook.test.d.ts.map +1 -0
  33. package/dist/tests/claude-hook.test.js +76 -0
  34. package/dist/tests/claude-hook.test.js.map +1 -0
  35. package/dist/tests/hook.test.d.ts +2 -0
  36. package/dist/tests/hook.test.d.ts.map +1 -0
  37. package/dist/tests/hook.test.js +62 -0
  38. package/dist/tests/hook.test.js.map +1 -0
  39. package/dist/tests/skeletonizer.test.d.ts +2 -0
  40. package/dist/tests/skeletonizer.test.d.ts.map +1 -0
  41. package/dist/tests/skeletonizer.test.js +71 -0
  42. package/dist/tests/skeletonizer.test.js.map +1 -0
  43. package/package.json +42 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kevin Bermudez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # 🛡️ ContextGuard
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
4
+ [![Node: >=18](https://img.shields.io/badge/Node->=18.0.0-green.svg)](https://nodejs.org/)
5
+ [![pnpm: >=10](https://img.shields.io/badge/pnpm->=10.0.0-F69220.svg?logo=pnpm&logoColor=white)](https://pnpm.io/)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-7.0%20(Go%20Native)-blue.svg)](https://www.typescriptlang.org/)
7
+ [![Go Engine](https://img.shields.io/badge/Compiler-Go%20Native%20(Corsa)-00ADD8.svg)](https://golang.org/)
8
+
9
+ > **Intelligent context admission control & token optimization for Claude Code, Cursor, and Agentic AI workflows.**
10
+
11
+ Stop burning expensive tokens and degrading reasoning accuracy. `ContextGuard` intercepts un-scoped, large file reads before they enter your frontier reasoning models (Claude Opus 5, Claude Sonnet 5, GPT-6 Astra), extracting structural signatures or delegating summarization to ultra-fast worker models (Gemini 3.8 Flash, local SLMs).
12
+
13
+ ---
14
+
15
+ ## ⚡ High-Performance Go Native Compiler (TypeScript 7.0)
16
+
17
+ ContextGuard is built on **TypeScript 7.0**, utilizing the native **Go-based compiler engine** (*Corsa*):
18
+ * **10x Faster Compilation:** Type-checking and build steps run directly on native machine code compiled in Go, taking full advantage of Apple Silicon / ARM64 and multi-threaded Goroutines.
19
+ * **Sub-Millisecond Cold Starts:** Pure ESM standard library design with zero production runtime dependencies ensures the Claude Code `PreToolUse` hook executes in under **15ms**.
20
+
21
+ ---
22
+
23
+ ## 💡 The Problem
24
+
25
+ Frontier models are incredible at reasoning, but reading massive files (1,000+ lines of monorepo code, generated types, or verbose logs) introduces two major problems:
26
+
27
+ 1. **FinOps Bleed:** Frontier models cost between \$3.00 and \$15.00+ per million input tokens. Dumping a few 2,000-line files into a session quickly adds up to dozens of dollars per day per developer.
28
+ 2. **Context Rot & "Lost in the Middle":** Saturating attention heads with hundreds of lines of boilerplate degrades precision, increases hallucination rates, and derails agentic reasoning loops.
29
+
30
+ ---
31
+
32
+ ## 🏗️ How ContextGuard Works
33
+
34
+ ContextGuard acts as an **admission turnstile** using Claude Code's native `PreToolUse` hook lifecycle:
35
+
36
+ ```
37
+ CLAUDE CODE TOOL INVOCATION
38
+
39
+
40
+ Does the file exceed the limit (e.g. 300 lines)
41
+ AND is it an un-scoped full read?
42
+
43
+ ┌────────────┴────────────┐
44
+ YES NO
45
+ │ │
46
+ ▼ ▼
47
+ ┌───────────────────────────┐ [ALLOW: Exit 0]
48
+ │ CONTEXTGUARD TRIGGER │ File is small or
49
+ └─────────────┬─────────────┘ targeted slice requested
50
+
51
+ ┌────────────┴────────────┐
52
+ ▼ ▼
53
+ [Tier 0: Local AST] [Tier 1: Worker Model]
54
+ TypeScript, Python, Go, Logs, JSON, Markdown or
55
+ Rust, Java, C# semantic question queries
56
+ • Cost: $0.00 • Gemini Flash / Ollama
57
+ • Latency: < 5ms • Cost: ~95% cheaper
58
+ │ │
59
+ └────────────┬────────────┘
60
+
61
+
62
+ [HARD BLOCK: Exit 2]
63
+ Emits signature skeleton with [L120] line tags to stderr
64
+
65
+
66
+ Claude receives system feedback & requests
67
+ the exact slice needed (e.g. offset=130, limit=40)
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 🚀 Quick Start for Claude Code
73
+
74
+ ### 1. Installation
75
+
76
+ Clone or install in your workspace:
77
+
78
+ ```bash
79
+ pnpm add -D @kevinbermudezc/context-guard
80
+ # Or global install:
81
+ pnpm add -g @kevinbermudezc/context-guard
82
+ ```
83
+
84
+ ### 2. Configure Claude Code Hook
85
+
86
+ Add ContextGuard to your `.claude/settings.json` (at project root or `~/.claude/settings.json` globally):
87
+
88
+ ```json
89
+ {
90
+ "hooks": {
91
+ "PreToolUse": [
92
+ {
93
+ "matcher": "View|Bash",
94
+ "type": "command",
95
+ "command": "node ./node_modules/@kevinbermudezc/context-guard/dist/bin/claude-hook.js"
96
+ }
97
+ ]
98
+ }
99
+ }
100
+ ```
101
+
102
+ That's it! When Claude Code attempts to run `View` or bash commands like `cat massive-file.ts`, ContextGuard will intercept it, extract the structural map, and instruct Claude to seek only the exact line range it needs.
103
+
104
+ ---
105
+
106
+ ## 🛠️ CLI Usage
107
+
108
+ You can also use ContextGuard directly as a command-line tool:
109
+
110
+ ```bash
111
+ # View file through ContextGuard (auto-shunts if > 300 lines)
112
+ npx @kevinbermudezc/context-guard src/large-service.ts
113
+
114
+ # Ask a semantic question to the worker model
115
+ npx @kevinbermudezc/context-guard logs/server.log --query "Find all 500 status database connection timeouts"
116
+
117
+ # Specific line range (bypasses guard / passthrough)
118
+ npx @kevinbermudezc/context-guard src/large-service.ts --start 120 --end 160
119
+
120
+ # Output structured JSON
121
+ npx @kevinbermudezc/context-guard src/large-service.ts --json
122
+ ```
123
+
124
+ ---
125
+
126
+ ## ⚙️ Configuration
127
+
128
+ Customize thresholds and worker models via environment variables:
129
+
130
+ | Variable | Default | Description |
131
+ | :--- | :--- | :--- |
132
+ | `CONTEXT_GUARD_MAX_LINES` | `300` | Maximum lines allowed for un-scoped direct reads |
133
+ | `CONTEXT_GUARD_MAX_BYTES` | `25600` | Maximum file size in bytes (~25 KB) |
134
+ | `CONTEXT_GUARD_PROVIDER` | `skeleton` | Worker mode: `skeleton` (Tier 0 AST), `gemini`, `ollama`, or `openai` |
135
+ | `GEMINI_API_KEY` | - | API key for Gemini 2.5 Flash worker |
136
+ | `GEMINI_MODEL` | `gemini-3.8-flash` | Gemini model name |
137
+ | `OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama local endpoint |
138
+ | `OLLAMA_MODEL` | `qwen2.5-coder:7b` | Model to use in Ollama |
139
+
140
+ ---
141
+
142
+ ## 🧪 Development & Testing
143
+
144
+ ```bash
145
+ # Install dependencies with strict symlink resolution
146
+ pnpm install
147
+
148
+ # Compile TypeScript using Go-powered tsc engine
149
+ pnpm run build
150
+
151
+ # Run unit and end-to-end tests
152
+ pnpm test
153
+ ```
154
+
155
+ ---
156
+
157
+ ## 📄 License
158
+
159
+ MIT © [Kevin Bermudez](https://github.com/kevinbermudezc)
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ============================================================================
4
+ * ContextGuard PreToolUse Hook for Claude Code
5
+ * Intercepts un-scoped large file reads and prevents context saturation.
6
+ * Returns Exit Code 2 (Hard Block) with structured signatures to stderr.
7
+ * ============================================================================
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=claude-hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-hook.d.ts","sourceRoot":"","sources":["../../bin/claude-hook.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG"}
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ============================================================================
4
+ * ContextGuard PreToolUse Hook for Claude Code
5
+ * Intercepts un-scoped large file reads and prevents context saturation.
6
+ * Returns Exit Code 2 (Hard Block) with structured signatures to stderr.
7
+ * ============================================================================
8
+ */
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { CONFIG } from '../src/config.js';
12
+ import { extractCodeSkeleton } from '../src/skeletonizer.js';
13
+ async function runHook() {
14
+ const inputChunks = [];
15
+ for await (const chunk of process.stdin) {
16
+ inputChunks.push(chunk);
17
+ }
18
+ if (inputChunks.length === 0) {
19
+ process.exit(0);
20
+ }
21
+ let payload;
22
+ try {
23
+ payload = JSON.parse(Buffer.concat(inputChunks).toString('utf-8'));
24
+ }
25
+ catch {
26
+ process.exit(0); // If stdin is not valid JSON, allow
27
+ }
28
+ const toolName = payload.tool_name || payload.name;
29
+ const toolInput = payload.tool_input || payload.input || {};
30
+ let targetFilePath = null;
31
+ let isScoped = false;
32
+ // 1. Intercept "View" or "view_file" tools
33
+ if (toolName === 'View' || toolName === 'view_file') {
34
+ targetFilePath = toolInput.file_path || toolInput.path || toolInput.AbsolutePath || null;
35
+ // Check if Claude specified line bounds
36
+ if (toolInput.offset !== undefined ||
37
+ toolInput.limit !== undefined ||
38
+ toolInput.StartLine !== undefined ||
39
+ toolInput.EndLine !== undefined) {
40
+ isScoped = true;
41
+ }
42
+ }
43
+ // 2. Intercept Bash commands doing open reads (cat, less, more, head)
44
+ if (toolName === 'Bash' || toolName === 'bash') {
45
+ const cmd = (toolInput.command || '').trim();
46
+ // Allow piped/filtered commands (e.g. cat file | grep foo, or head -n 20)
47
+ if (cmd.includes('|') || cmd.includes('>') || cmd.startsWith('head -n') || cmd.startsWith('tail -n')) {
48
+ process.exit(0);
49
+ }
50
+ const catMatch = cmd.match(/^(?:cat|less|more)\s+(["']?)([^"'\s]+)\1$/);
51
+ if (catMatch) {
52
+ targetFilePath = catMatch[2];
53
+ }
54
+ }
55
+ // If not a reading tool or already scoped, allow execution immediately
56
+ if (!targetFilePath || isScoped) {
57
+ process.exit(0); // Exit 0 = ALLOW
58
+ }
59
+ const resolvedPath = path.resolve(process.cwd(), targetFilePath);
60
+ if (!fs.existsSync(resolvedPath) || fs.statSync(resolvedPath).isDirectory()) {
61
+ process.exit(0);
62
+ }
63
+ const stat = fs.statSync(resolvedPath);
64
+ const rawContent = fs.readFileSync(resolvedPath, 'utf-8');
65
+ const lines = rawContent.split('\n');
66
+ const totalLines = lines.length;
67
+ const ext = path.extname(resolvedPath);
68
+ // If within safety thresholds, allow direct read
69
+ if (totalLines <= CONFIG.maxLines && stat.size <= CONFIG.maxBytes) {
70
+ process.exit(0); // Exit 0 = ALLOW
71
+ }
72
+ // ========================================================================
73
+ // APPLY CONTEXTGUARD SHUNT (BLOCK AND EMIT SKELETON)
74
+ // ========================================================================
75
+ const skeleton = extractCodeSkeleton(rawContent, ext);
76
+ const preview = skeleton || lines.slice(0, 40).join('\n') + `\n... [${totalLines - 40} lines truncated by ContextGuard]`;
77
+ const feedbackMessage = [
78
+ `🛡️ [CONTEXTGUARD: LECTURA REGULADA - PREVENCIÓN DE SATURACIÓN DE TOKENS]`,
79
+ `El recurso "${path.basename(resolvedPath)}" contiene ${totalLines} líneas (~${(stat.size / 1024).toFixed(1)} KB).`,
80
+ `Para optimizar tu contexto y prevenir degradación de razonamiento, no se ha cargado el archivo completo en la ventana principal.`,
81
+ ``,
82
+ `📋 ESQUELETO ESTRUCTURAL Y FIRMAS (Con índices de línea):`,
83
+ `--------------------------------------------------------------------------------`,
84
+ preview,
85
+ `--------------------------------------------------------------------------------`,
86
+ ``,
87
+ `🎯 ACCIÓN RECOMENDADA:`,
88
+ `Revisa las firmas anteriores y ejecuta la herramienta "View" especificando los parámetros "offset" y "limit" (o StartLine y EndLine) para inspeccionar únicamente la sección necesaria.`
89
+ ].join('\n');
90
+ // Emit to stderr and exit with code 2 to trigger a controlled block with system feedback in Claude Code
91
+ process.stderr.write(feedbackMessage + '\n');
92
+ process.exit(2);
93
+ }
94
+ runHook().catch(() => process.exit(0));
95
+ //# sourceMappingURL=claude-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-hook.js","sourceRoot":"","sources":["../../bin/claude-hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=context-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-guard.d.ts","sourceRoot":"","sources":["../../bin/context-guard.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { processWithContextGuard } from '../src/index.js';
4
+ async function runCli() {
5
+ const args = process.argv.slice(2);
6
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
7
+ console.log(`
8
+ ContextGuard CLI v1.0.0
9
+ Intelligent context admission control & token optimization
10
+
11
+ Usage:
12
+ context-guard <file-path> [options]
13
+
14
+ Options:
15
+ --query, -q <text> Semantic search / intent filter for worker model
16
+ --start <num> Start line number (1-indexed slice passthrough)
17
+ --end <num> End line number (1-indexed slice passthrough)
18
+ --force-worker Force routing through worker model
19
+ --json Output result in JSON format
20
+ --help, -h Show this help menu
21
+ `);
22
+ process.exit(0);
23
+ }
24
+ const filePath = path.resolve(args[0]);
25
+ let query = '';
26
+ let startLine;
27
+ let endLine;
28
+ let forceWorker = false;
29
+ let outputJson = false;
30
+ for (let i = 1; i < args.length; i++) {
31
+ if (args[i] === '--query' || args[i] === '-q')
32
+ query = args[++i];
33
+ if (args[i] === '--start')
34
+ startLine = parseInt(args[++i], 10);
35
+ if (args[i] === '--end')
36
+ endLine = parseInt(args[++i], 10);
37
+ if (args[i] === '--force-worker')
38
+ forceWorker = true;
39
+ if (args[i] === '--json')
40
+ outputJson = true;
41
+ }
42
+ const result = await processWithContextGuard({
43
+ filePath,
44
+ query,
45
+ startLine,
46
+ endLine,
47
+ forceWorker
48
+ });
49
+ if (outputJson) {
50
+ console.log(JSON.stringify(result, null, 2));
51
+ }
52
+ else {
53
+ if (result.error) {
54
+ console.error(`[Error] ${result.error}`);
55
+ process.exit(1);
56
+ }
57
+ console.log(result.content);
58
+ }
59
+ }
60
+ runCli().catch((err) => {
61
+ console.error(err);
62
+ process.exit(1);
63
+ });
64
+ //# sourceMappingURL=context-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-guard.js","sourceRoot":"","sources":["../../bin/context-guard.ts"],"names":[],"mappings":";AAEA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,KAAK,UAAU,MAAM;IACnB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;KAcX,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,SAA6B,CAAC;IAClC,IAAI,OAA2B,CAAC;IAChC,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACjE,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/D,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO;YAAE,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3D,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,gBAAgB;YAAE,WAAW,GAAG,IAAI,CAAC;QACrD,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ;YAAE,UAAU,GAAG,IAAI,CAAC;IAC9C,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC;QAC3C,QAAQ;QACR,KAAK;QACL,SAAS;QACT,OAAO;QACP,WAAW;KACZ,CAAC,CAAC;IAEH,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,WAAW,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ContextGuardConfig } from './types.js';
2
+ export declare const CONFIG: ContextGuardConfig;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,YAAY,CAAC;AAErE,eAAO,MAAM,MAAM,EAAE,kBAiBpB,CAAC"}
@@ -0,0 +1,16 @@
1
+ export const CONFIG = {
2
+ maxLines: parseInt(process.env.CONTEXT_GUARD_MAX_LINES || '300', 10),
3
+ maxBytes: parseInt(process.env.CONTEXT_GUARD_MAX_BYTES || `${25 * 1024}`, 10), // 25 KB
4
+ provider: (process.env.CONTEXT_GUARD_PROVIDER || 'gemini'),
5
+ // Gemini Configuration
6
+ geminiApiKey: process.env.GEMINI_API_KEY || '',
7
+ geminiModel: process.env.GEMINI_MODEL || 'gemini-3.8-flash',
8
+ // Ollama Configuration
9
+ ollamaUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
10
+ ollamaModel: process.env.OLLAMA_MODEL || 'qwen2.5-coder:7b',
11
+ // OpenAI-compatible Configuration
12
+ openaiBaseUrl: process.env.OPENAI_BASE_URL || '',
13
+ openaiApiKey: process.env.OPENAI_API_KEY || '',
14
+ openaiModel: process.env.OPENAI_MODEL || 'gpt-5.6-luna'
15
+ };
16
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,MAAM,GAAuB;IACxC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,KAAK,EAAE,EAAE,CAAC;IACpE,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,QAAQ;IACvF,QAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,QAAQ,CAAmB;IAE5E,uBAAuB;IACvB,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE;IAC9C,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,kBAAkB;IAE3D,uBAAuB;IACvB,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,wBAAwB;IAClE,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,kBAAkB;IAE3D,kCAAkC;IAClC,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE;IAChD,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE;IAC9C,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,cAAc;CACxD,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { ProcessFileOptions, ProcessFileResult } from './types.js';
2
+ export * from './types.js';
3
+ export * from './config.js';
4
+ export * from './skeletonizer.js';
5
+ export * from './worker-model.js';
6
+ /**
7
+ * Main engine of ContextGuard
8
+ * Evaluates file size and either passes it through or shunts it to an AST/Worker summary.
9
+ */
10
+ export declare function processWithContextGuard(options: ProcessFileOptions): Promise<ProcessFileResult>;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAExE,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAElC;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoGrG"}
@@ -0,0 +1,108 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { CONFIG } from './config.js';
4
+ import { extractCodeSkeleton } from './skeletonizer.js';
5
+ import { callWorkerModel } from './worker-model.js';
6
+ export * from './types.js';
7
+ export * from './config.js';
8
+ export * from './skeletonizer.js';
9
+ export * from './worker-model.js';
10
+ /**
11
+ * Main engine of ContextGuard
12
+ * Evaluates file size and either passes it through or shunts it to an AST/Worker summary.
13
+ */
14
+ export async function processWithContextGuard(options) {
15
+ const { filePath, query = '', startLine, endLine, forceWorker = false } = options;
16
+ const resolved = path.resolve(process.cwd(), filePath);
17
+ if (!fs.existsSync(resolved) || fs.statSync(resolved).isDirectory()) {
18
+ return {
19
+ status: 'ERROR',
20
+ error: `File not found or is a directory: ${filePath}`,
21
+ content: ''
22
+ };
23
+ }
24
+ const stat = fs.statSync(resolved);
25
+ const rawContent = fs.readFileSync(resolved, 'utf-8');
26
+ const lines = rawContent.split('\n');
27
+ const totalLines = lines.length;
28
+ const ext = path.extname(resolved).toLowerCase();
29
+ // 1. Scoped line requests bypass ContextGuard (Passthrough)
30
+ if (startLine !== undefined || endLine !== undefined) {
31
+ const s = Math.max(1, startLine || 1) - 1;
32
+ const e = Math.min(totalLines, endLine || totalLines);
33
+ const sliced = lines.slice(s, e).join('\n');
34
+ return {
35
+ status: 'PASSTHROUGH_SLICE',
36
+ reason: `Scoped slice requested (lines ${s + 1} to ${e})`,
37
+ content: sliced,
38
+ totalLines,
39
+ sizeBytes: stat.size
40
+ };
41
+ }
42
+ // 2. Small files pass through directly
43
+ const isSmall = totalLines <= CONFIG.maxLines && stat.size <= CONFIG.maxBytes;
44
+ if (isSmall && !forceWorker) {
45
+ return {
46
+ status: 'PASSTHROUGH_FULL',
47
+ reason: `File within limits (${totalLines} lines, ${(stat.size / 1024).toFixed(1)} KB)`,
48
+ content: rawContent,
49
+ totalLines,
50
+ sizeBytes: stat.size
51
+ };
52
+ }
53
+ // 3. Shunt Tier 0: Structural AST / Skeleton (0 tokens, < 5ms)
54
+ if (!query && !forceWorker && CONFIG.provider !== 'gemini' && CONFIG.provider !== 'ollama' && CONFIG.provider !== 'openai') {
55
+ const skeleton = extractCodeSkeleton(rawContent, ext);
56
+ if (skeleton) {
57
+ return {
58
+ status: 'SHUNTED_LOCAL_AST',
59
+ totalLines,
60
+ sizeBytes: stat.size,
61
+ content: `<!-- CONTEXTGUARD: Large file (${totalLines} lines, ${(stat.size / 1024).toFixed(1)} KB). Extracted structural skeleton. -->\n` +
62
+ `<!-- Request specific line ranges (e.g. L120-L150) to inspect implementation. -->\n\n` +
63
+ skeleton
64
+ };
65
+ }
66
+ }
67
+ // 4. Shunt Tier 0 Fallback to AST if provider fails or if local AST requested
68
+ const localSkeleton = extractCodeSkeleton(rawContent, ext);
69
+ if (!query && localSkeleton && CONFIG.provider === 'skeleton') {
70
+ return {
71
+ status: 'SHUNTED_LOCAL_AST',
72
+ totalLines,
73
+ sizeBytes: stat.size,
74
+ content: `<!-- CONTEXTGUARD: Structural skeleton (Tier 0 AST) -->\n\n` + localSkeleton
75
+ };
76
+ }
77
+ // 5. Shunt Tier 1: Worker Model
78
+ try {
79
+ const summary = await callWorkerModel(rawContent, query, ext);
80
+ return {
81
+ status: 'SHUNTED_WORKER_MODEL',
82
+ provider: CONFIG.provider,
83
+ totalLines,
84
+ sizeBytes: stat.size,
85
+ content: `<!-- CONTEXTGUARD: File of ${totalLines} lines summarized by Worker Model (${CONFIG.provider}) -->\n` +
86
+ `<!-- Request specific line ranges using offset/limit to read full blocks. -->\n\n` +
87
+ summary
88
+ };
89
+ }
90
+ catch (err) {
91
+ // If worker model fails, fallback to local skeleton or first 50 lines
92
+ if (localSkeleton) {
93
+ return {
94
+ status: 'SHUNTED_LOCAL_AST',
95
+ warning: `Worker failed (${err.message}). Fell back to local AST skeleton.`,
96
+ totalLines,
97
+ sizeBytes: stat.size,
98
+ content: localSkeleton
99
+ };
100
+ }
101
+ return {
102
+ status: 'SHUNT_FALLBACK',
103
+ warning: `Worker failed: ${err.message}. Showing head of file.`,
104
+ content: lines.slice(0, 50).join('\n') + `\n\n... [${totalLines - 50} lines truncated by ContextGuard] ...`
105
+ };
106
+ }
107
+ }
108
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tier 0 Structural Skeletonizer
3
+ * Extracts structural declarations, signatures, imports, and types with line numbers.
4
+ * Cost: $0.00 | Execution: < 5ms
5
+ */
6
+ export declare function isSupportedExtension(ext: string): string | null;
7
+ export declare function extractCodeSkeleton(content: string, ext: string): string | null;
8
+ //# sourceMappingURL=skeletonizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeletonizer.d.ts","sourceRoot":"","sources":["../../src/skeletonizer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiCH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU/D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiC/E"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Tier 0 Structural Skeletonizer
3
+ * Extracts structural declarations, signatures, imports, and types with line numbers.
4
+ * Cost: $0.00 | Execution: < 5ms
5
+ */
6
+ const LANGUAGE_PATTERNS = {
7
+ // TypeScript & JavaScript
8
+ ts: [
9
+ /^(export\s+)?(default\s+)?(class|interface|type|enum)\s+([A-Za-z0-9_]+)/,
10
+ /^(export\s+)?(async\s+)?function\s+([A-Za-z0-9_]+)\s*\(/,
11
+ /^(export\s+)?const\s+([A-Za-z0-9_]+)\s*=\s*(async\s*)?\(/,
12
+ /^(export\s+)?((public|private|protected|static|async|\*)\s+)*([A-Za-z0-9_]+)\s*\([^)]*\)\s*[:{]/
13
+ ],
14
+ // Python
15
+ py: [
16
+ /^(async\s+)?def\s+([A-Za-z0-9_]+)\s*\(/,
17
+ /^class\s+([A-Za-z0-9_]+)(\s*\([^)]*\))?:/
18
+ ],
19
+ // Go
20
+ go: [
21
+ /^func\s+(\([^)]+\)\s+)?([A-Za-z0-9_]+)\s*\(/,
22
+ /^type\s+([A-Za-z0-9_]+)\s+(struct|interface)/
23
+ ],
24
+ // Rust
25
+ rs: [
26
+ /^(pub\s+)?(async\s+)?fn\s+([A-Za-z0-9_]+)/,
27
+ /^(pub\s+)?(struct|enum|trait|type)\s+([A-Za-z0-9_]+)/,
28
+ /^impl(\s+<[^>]+>)?\s+([A-Za-z0-9_]+)/
29
+ ],
30
+ // Java / C#
31
+ java: [
32
+ /^(public|private|protected|static|final|native|synchronized|abstract|\s+)+[\w\<\>\[\]]+\s+([A-Za-z0-9_]+)\s*\([^\)]*\)\s*(\{)?/,
33
+ /^(public|private|protected|static|abstract|\s+)+(class|interface|enum|record)\s+([A-Za-z0-9_]+)/
34
+ ]
35
+ };
36
+ export function isSupportedExtension(ext) {
37
+ const normalized = ext.toLowerCase().replace(/^\./, '');
38
+ const mapping = {
39
+ ts: 'ts', tsx: 'ts', js: 'ts', jsx: 'ts', mjs: 'ts', cjs: 'ts',
40
+ py: 'py',
41
+ go: 'go',
42
+ rs: 'rs',
43
+ java: 'java', cs: 'java'
44
+ };
45
+ return mapping[normalized] || null;
46
+ }
47
+ export function extractCodeSkeleton(content, ext) {
48
+ const langKey = isSupportedExtension(ext);
49
+ if (!langKey)
50
+ return null;
51
+ const patterns = LANGUAGE_PATTERNS[langKey] || [];
52
+ const lines = content.split('\n');
53
+ const skeleton = [];
54
+ lines.forEach((line, index) => {
55
+ const lineNum = index + 1;
56
+ const trimmed = line.trim();
57
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('/*')) {
58
+ return;
59
+ }
60
+ // Keep top-level import/from statements
61
+ if (trimmed.startsWith('import ') || trimmed.startsWith('from ') || (langKey === 'go' && trimmed.startsWith('import ('))) {
62
+ skeleton.push(`[L${lineNum}] ${trimmed}`);
63
+ return;
64
+ }
65
+ for (const pattern of patterns) {
66
+ if (pattern.test(trimmed)) {
67
+ // Strip trailing curly braces or semicolons for brevity
68
+ const clean = trimmed.replace(/\{\s*$/, '').trim();
69
+ skeleton.push(`[L${lineNum}] ${clean}`);
70
+ break;
71
+ }
72
+ }
73
+ });
74
+ return skeleton.length > 0 ? skeleton.join('\n') : null;
75
+ }
76
+ //# sourceMappingURL=skeletonizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeletonizer.js","sourceRoot":"","sources":["../../src/skeletonizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ export type WorkerProvider = 'gemini' | 'ollama' | 'openai' | 'skeleton';
2
+ export interface ContextGuardConfig {
3
+ maxLines: number;
4
+ maxBytes: number;
5
+ provider: WorkerProvider;
6
+ geminiApiKey?: string;
7
+ geminiModel?: string;
8
+ ollamaUrl?: string;
9
+ ollamaModel?: string;
10
+ openaiBaseUrl?: string;
11
+ openaiApiKey?: string;
12
+ openaiModel?: string;
13
+ }
14
+ export interface ProcessFileOptions {
15
+ filePath: string;
16
+ query?: string;
17
+ startLine?: number;
18
+ endLine?: number;
19
+ forceWorker?: boolean;
20
+ }
21
+ export type ProcessFileStatus = 'PASSTHROUGH_FULL' | 'PASSTHROUGH_SLICE' | 'SHUNTED_LOCAL_AST' | 'SHUNTED_WORKER_MODEL' | 'SHUNT_FALLBACK' | 'ERROR';
22
+ export interface ProcessFileResult {
23
+ status: ProcessFileStatus;
24
+ content: string;
25
+ totalLines?: number;
26
+ sizeBytes?: number;
27
+ reason?: string;
28
+ provider?: string;
29
+ warning?: string;
30
+ error?: string;
31
+ }
32
+ export interface ClaudeToolInput {
33
+ file_path?: string;
34
+ path?: string;
35
+ AbsolutePath?: string;
36
+ offset?: number;
37
+ limit?: number;
38
+ StartLine?: number;
39
+ EndLine?: number;
40
+ command?: string;
41
+ [key: string]: unknown;
42
+ }
43
+ export interface ClaudeToolPayload {
44
+ tool_name?: string;
45
+ name?: string;
46
+ tool_input?: ClaudeToolInput;
47
+ input?: ClaudeToolInput;
48
+ }
49
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEzE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,iBAAiB,GACzB,kBAAkB,GAClB,mBAAmB,GACnB,mBAAmB,GACnB,sBAAsB,GACtB,gBAAgB,GAChB,OAAO,CAAC;AAEZ,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export declare function callWorkerModel(content: string, query?: string, ext?: string): Promise<string>;
2
+ //# sourceMappingURL=worker-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-model.d.ts","sourceRoot":"","sources":["../../src/worker-model.ts"],"names":[],"mappings":"AAEA,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,EAAE,GAAG,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAoF5G"}
@@ -0,0 +1,81 @@
1
+ import { CONFIG } from './config.js';
2
+ export async function callWorkerModel(content, query = '', ext = '') {
3
+ const systemPrompt = `Eres ContextGuard, un asistente de optimización de contexto y costes para IA.
4
+ Tu misión es procesar un archivo grande y devolver ÚNICAMENTE una representación ultra compacta y precisa para el modelo principal.
5
+ Reglas:
6
+ 1. Incluye las firmas de funciones y métodos, clases, tipos, interfaces y rutas expuestas.
7
+ 2. Anota los números de línea aproximados donde se ubica cada sección.
8
+ 3. Omite implementaciones internas, boilerplate, logs repetitivos o comentarios innecesarios.
9
+ 4. Si se proporcionó una intención/query, enfócate estrictamente en lo relevante para esa intención.
10
+ 5. Responde directamente con Markdown conciso, sin preámbulos ni saludos.`;
11
+ const userPrompt = query
12
+ ? `INTENCIÓN ESPECÍFICA: "${query}"\n\nCONTENIDO DEL ARCHIVO (${ext}):\n\`\`\`\n${content}\n\`\`\``
13
+ : `Genera un mapa de estructura y firmas con números de línea para este archivo (${ext}):\n\`\`\`\n${content}\n\`\`\``;
14
+ // 1. Proveedor Gemini Flash
15
+ if (CONFIG.provider === 'gemini') {
16
+ if (!CONFIG.geminiApiKey) {
17
+ throw new Error('GEMINI_API_KEY no configurada. Define la variable de entorno o usa CONFIG_GUARD_PROVIDER=skeleton.');
18
+ }
19
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${CONFIG.geminiModel}:generateContent?key=${CONFIG.geminiApiKey}`;
20
+ const res = await fetch(url, {
21
+ method: 'POST',
22
+ headers: { 'Content-Type': 'application/json' },
23
+ body: JSON.stringify({
24
+ contents: [{ role: 'user', parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] }],
25
+ generationConfig: { temperature: 0.1, maxOutputTokens: 1500 }
26
+ })
27
+ });
28
+ if (!res.ok) {
29
+ const err = await res.text();
30
+ throw new Error(`Error en Gemini Flash API: ${res.status} - ${err}`);
31
+ }
32
+ const data = await res.json();
33
+ return data.candidates?.[0]?.content?.parts?.[0]?.text || 'No se pudo generar el resumen.';
34
+ }
35
+ // 2. Proveedor Ollama Local (0 costo / offline)
36
+ if (CONFIG.provider === 'ollama') {
37
+ const res = await fetch(`${CONFIG.ollamaUrl}/api/generate`, {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify({
41
+ model: CONFIG.ollamaModel,
42
+ system: systemPrompt,
43
+ prompt: userPrompt,
44
+ stream: false
45
+ })
46
+ });
47
+ if (!res.ok) {
48
+ throw new Error(`Error conectando con Ollama en ${CONFIG.ollamaUrl}`);
49
+ }
50
+ const data = await res.json();
51
+ return data.response || 'No se pudo generar el resumen.';
52
+ }
53
+ // 3. Proveedor OpenAI / compatible
54
+ if (CONFIG.provider === 'openai') {
55
+ const baseUrl = CONFIG.openaiBaseUrl || 'https://api.openai.com/v1';
56
+ const res = await fetch(`${baseUrl}/chat/completions`, {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ 'Authorization': `Bearer ${CONFIG.openaiApiKey}`
61
+ },
62
+ body: JSON.stringify({
63
+ model: CONFIG.openaiModel,
64
+ messages: [
65
+ { role: 'system', content: systemPrompt },
66
+ { role: 'user', content: userPrompt }
67
+ ],
68
+ temperature: 0.1,
69
+ max_tokens: 1500
70
+ })
71
+ });
72
+ if (!res.ok) {
73
+ const err = await res.text();
74
+ throw new Error(`Error en API OpenAI/compatible: ${res.status} - ${err}`);
75
+ }
76
+ const data = await res.json();
77
+ return data.choices?.[0]?.message?.content || 'No se pudo generar el resumen.';
78
+ }
79
+ throw new Error(`Proveedor no soportado: ${CONFIG.provider}`);
80
+ }
81
+ //# sourceMappingURL=worker-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-model.js","sourceRoot":"","sources":["../../src/worker-model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=claude-hook.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-hook.test.d.ts","sourceRoot":"","sources":["../../tests/claude-hook.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { spawn } from 'node:child_process';
4
+ import path from 'node:path';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ const HOOK_PATH = path.resolve(process.cwd(), 'dist/bin/claude-hook.js');
8
+ function runHookWithStdin(payload) {
9
+ return new Promise((resolve) => {
10
+ const proc = spawn('node', [HOOK_PATH], {
11
+ env: { ...process.env, CONTEXT_GUARD_MAX_LINES: '100' }
12
+ });
13
+ let stdout = '';
14
+ let stderr = '';
15
+ proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
16
+ proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
17
+ proc.on('close', (code) => {
18
+ resolve({ exitCode: code ?? 0, stderr, stdout });
19
+ });
20
+ proc.stdin.write(JSON.stringify(payload));
21
+ proc.stdin.end();
22
+ });
23
+ }
24
+ describe('Claude Code Hook PreToolUse End-to-End', () => {
25
+ it('should block un-scoped View of a file over threshold with Exit Code 2', async () => {
26
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-hook-'));
27
+ const tempFile = path.join(tempDir, 'huge.ts');
28
+ // Create 150 lines of code
29
+ const lines = ['export class OrderService {'];
30
+ for (let i = 0; i < 150; i++) {
31
+ lines.push(` // dummy line ${i}`);
32
+ }
33
+ lines.push(' public pay() { return true; }');
34
+ lines.push('}');
35
+ fs.writeFileSync(tempFile, lines.join('\n'));
36
+ const res = await runHookWithStdin({
37
+ tool_name: 'View',
38
+ tool_input: { file_path: tempFile }
39
+ });
40
+ assert.strictEqual(res.exitCode, 2, 'Must exit with 2 to hard block Claude Code');
41
+ assert.ok(res.stderr.includes('CONTEXTGUARD: LECTURA REGULADA'), 'Must emit warning in stderr');
42
+ assert.ok(res.stderr.includes('OrderService'), 'Must include skeleton in stderr');
43
+ fs.rmSync(tempDir, { recursive: true });
44
+ });
45
+ it('should allow (exit 0) when View has offset and limit', async () => {
46
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-hook-'));
47
+ const tempFile = path.join(tempDir, 'huge.ts');
48
+ fs.writeFileSync(tempFile, Array.from({ length: 200 }, () => 'code').join('\n'));
49
+ const res = await runHookWithStdin({
50
+ tool_name: 'View',
51
+ tool_input: {
52
+ file_path: tempFile,
53
+ offset: 10,
54
+ limit: 20
55
+ }
56
+ });
57
+ assert.strictEqual(res.exitCode, 0, 'Must exit with 0 to allow Claude Code execution');
58
+ assert.strictEqual(res.stderr, '');
59
+ fs.rmSync(tempDir, { recursive: true });
60
+ });
61
+ it('should block open cat commands on large files with Exit Code 2', async () => {
62
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-hook-'));
63
+ const tempFile = path.join(tempDir, 'huge.ts');
64
+ fs.writeFileSync(tempFile, Array.from({ length: 200 }, () => 'code').join('\n'));
65
+ const res = await runHookWithStdin({
66
+ tool_name: 'Bash',
67
+ tool_input: {
68
+ command: `cat ${tempFile}`
69
+ }
70
+ });
71
+ assert.strictEqual(res.exitCode, 2, 'Must block raw cat with exit 2');
72
+ assert.ok(res.stderr.includes('CONTEXTGUARD'));
73
+ fs.rmSync(tempDir, { recursive: true });
74
+ });
75
+ });
76
+ //# sourceMappingURL=claude-hook.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-hook.test.js","sourceRoot":"","sources":["../../tests/claude-hook.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hook.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.test.d.ts","sourceRoot":"","sources":["../../tests/hook.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import { processWithContextGuard } from '../src/index.js';
7
+ describe('ContextGuard Engine Core', () => {
8
+ it('should passthrough small files directly', async () => {
9
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-test-'));
10
+ const tempFile = path.join(tempDir, 'small.ts');
11
+ fs.writeFileSync(tempFile, 'const x = 1;\nconsole.log(x);');
12
+ const result = await processWithContextGuard({
13
+ filePath: tempFile
14
+ });
15
+ assert.strictEqual(result.status, 'PASSTHROUGH_FULL');
16
+ assert.strictEqual(result.content, 'const x = 1;\nconsole.log(x);');
17
+ assert.strictEqual(result.totalLines, 2);
18
+ fs.rmSync(tempDir, { recursive: true });
19
+ });
20
+ it('should passthrough sliced ranges when startLine/endLine are requested', async () => {
21
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-test-'));
22
+ const tempFile = path.join(tempDir, 'big.ts');
23
+ const content = Array.from({ length: 400 }, (_, i) => `line ${i + 1}`).join('\n');
24
+ fs.writeFileSync(tempFile, content);
25
+ const result = await processWithContextGuard({
26
+ filePath: tempFile,
27
+ startLine: 10,
28
+ endLine: 15
29
+ });
30
+ assert.strictEqual(result.status, 'PASSTHROUGH_SLICE');
31
+ assert.strictEqual(result.totalLines, 400);
32
+ assert.ok(result.content.includes('line 10'));
33
+ assert.ok(result.content.includes('line 15'));
34
+ assert.ok(!result.content.includes('line 16'));
35
+ fs.rmSync(tempDir, { recursive: true });
36
+ });
37
+ it('should extract AST skeleton when file exceeds threshold without slice', async () => {
38
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-test-'));
39
+ const tempFile = path.join(tempDir, 'large.ts');
40
+ // Create 350 lines with some exports
41
+ const lines = [
42
+ "import { foo } from './foo.js';",
43
+ "export class BigService {"
44
+ ];
45
+ for (let i = 0; i < 350; i++) {
46
+ lines.push(` // comment line ${i}`);
47
+ }
48
+ lines.push(" public executeAction() { return true; }");
49
+ lines.push("}");
50
+ fs.writeFileSync(tempFile, lines.join('\n'));
51
+ // Test with skeleton provider
52
+ process.env.CONTEXT_GUARD_PROVIDER = 'skeleton';
53
+ const result = await processWithContextGuard({
54
+ filePath: tempFile
55
+ });
56
+ assert.strictEqual(result.status, 'SHUNTED_LOCAL_AST');
57
+ assert.ok(result.content.includes('export class BigService'));
58
+ assert.ok(result.content.includes('public executeAction()'));
59
+ fs.rmSync(tempDir, { recursive: true });
60
+ });
61
+ });
62
+ //# sourceMappingURL=hook.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.test.js","sourceRoot":"","sources":["../../tests/hook.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=skeletonizer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeletonizer.test.d.ts","sourceRoot":"","sources":["../../tests/skeletonizer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { extractCodeSkeleton, isSupportedExtension } from '../src/skeletonizer.js';
4
+ describe('Skeletonizer Tier 0 Extraction', () => {
5
+ it('should identify supported extensions', () => {
6
+ assert.strictEqual(isSupportedExtension('.ts'), 'ts');
7
+ assert.strictEqual(isSupportedExtension('.py'), 'py');
8
+ assert.strictEqual(isSupportedExtension('.go'), 'go');
9
+ assert.strictEqual(isSupportedExtension('.rs'), 'rs');
10
+ assert.strictEqual(isSupportedExtension('.java'), 'java');
11
+ assert.strictEqual(isSupportedExtension('.txt'), null);
12
+ });
13
+ it('should extract TypeScript classes, interfaces, and functions with line numbers', () => {
14
+ const tsCode = `
15
+ import { Config } from './config.js';
16
+ import path from 'path';
17
+
18
+ // Some comments
19
+ export interface UserProfile {
20
+ id: string;
21
+ name: string;
22
+ }
23
+
24
+ export class UserManager {
25
+ private cache: Map<string, UserProfile>;
26
+
27
+ public async fetchUser(id: string): Promise<UserProfile> {
28
+ // 50 lines of boilerplate logic
29
+ return { id, name: 'Alice' };
30
+ }
31
+ }
32
+
33
+ export function calculateTotal(items: number[]): number {
34
+ return items.reduce((a, b) => a + b, 0);
35
+ }
36
+ `.trim();
37
+ const skeleton = extractCodeSkeleton(tsCode, '.ts');
38
+ assert.ok(skeleton !== null);
39
+ if (!skeleton)
40
+ throw new Error('Skeleton is null');
41
+ assert.ok(skeleton.includes('[L1] import { Config } from \'./config.js\';'));
42
+ assert.ok(skeleton.includes('export interface UserProfile'));
43
+ assert.ok(skeleton.includes('export class UserManager'));
44
+ assert.ok(skeleton.includes('public async fetchUser(id: string)'));
45
+ assert.ok(skeleton.includes('export function calculateTotal(items: number[])'));
46
+ });
47
+ it('should extract Python classes and methods', () => {
48
+ const pyCode = `
49
+ import os
50
+ import sys
51
+
52
+ class DataProcessor:
53
+ def __init__(self, path: str):
54
+ self.path = path
55
+
56
+ def process(self):
57
+ pass
58
+
59
+ async def async_fetch():
60
+ pass
61
+ `.trim();
62
+ const skeleton = extractCodeSkeleton(pyCode, '.py');
63
+ assert.ok(skeleton !== null);
64
+ if (!skeleton)
65
+ throw new Error('Skeleton is null');
66
+ assert.ok(skeleton.includes('[L1] import os'));
67
+ assert.ok(skeleton.includes('class DataProcessor:'));
68
+ assert.ok(skeleton.includes('async def async_fetch()'));
69
+ });
70
+ });
71
+ //# sourceMappingURL=skeletonizer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeletonizer.test.js","sourceRoot":"","sources":["../../tests/skeletonizer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnF,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1D,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;KAsBd,CAAC,IAAI,EAAE,CAAC;QAET,MAAM,QAAQ,GAAG,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACpD,MAAM,CAAC,EAAE,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACnD,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,8CAA8C,CAAC,CAAC,CAAC;QAC7E,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,8BAA8B,CAAC,CAAC,CAAC;QAC7D,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAC,CAAC;QACzD,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,oCAAoC,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,iDAAiD,CAAC,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,MAAM,GAAG;;;;;;;;;;;;;KAad,CAAC,IAAI,EAAE,CAAC;QAET,MAAM,QAAQ,GAAG,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACpD,MAAM,CAAC,EAAE,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACnD,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@kevinbermudezc/context-guard",
3
+ "version": "1.0.0",
4
+ "description": "Intelligent context admission control and token optimization for Claude Code, Cursor, and Agentic workflows.",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "type": "module",
9
+ "main": "./dist/src/index.js",
10
+ "types": "./dist/src/index.d.ts",
11
+ "bin": {
12
+ "context-guard": "./dist/bin/context-guard.js",
13
+ "context-guard-hook": "./dist/bin/claude-hook.js"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "devDependencies": {
21
+ "@types/node": "^22.13.9",
22
+ "typescript": "^7.0.2"
23
+ },
24
+ "keywords": [
25
+ "claude-code",
26
+ "pretooluse-hook",
27
+ "ai-agent",
28
+ "token-optimization",
29
+ "finops",
30
+ "context-window",
31
+ "typescript"
32
+ ],
33
+ "author": "Kevin Bermudez",
34
+ "license": "MIT",
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc && chmod +x dist/bin/*.js",
40
+ "test": "pnpm run build && node --test dist/tests/**/*.test.js"
41
+ }
42
+ }