@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.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/bin/claude-hook.d.ts +10 -0
- package/dist/bin/claude-hook.d.ts.map +1 -0
- package/dist/bin/claude-hook.js +95 -0
- package/dist/bin/claude-hook.js.map +1 -0
- package/dist/bin/context-guard.d.ts +3 -0
- package/dist/bin/context-guard.d.ts.map +1 -0
- package/dist/bin/context-guard.js +64 -0
- package/dist/bin/context-guard.js.map +1 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +16 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +108 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/skeletonizer.d.ts +8 -0
- package/dist/src/skeletonizer.d.ts.map +1 -0
- package/dist/src/skeletonizer.js +76 -0
- package/dist/src/skeletonizer.js.map +1 -0
- package/dist/src/types.d.ts +49 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/worker-model.d.ts +2 -0
- package/dist/src/worker-model.d.ts.map +1 -0
- package/dist/src/worker-model.js +81 -0
- package/dist/src/worker-model.js.map +1 -0
- package/dist/tests/claude-hook.test.d.ts +2 -0
- package/dist/tests/claude-hook.test.d.ts.map +1 -0
- package/dist/tests/claude-hook.test.js +76 -0
- package/dist/tests/claude-hook.test.js.map +1 -0
- package/dist/tests/hook.test.d.ts +2 -0
- package/dist/tests/hook.test.d.ts.map +1 -0
- package/dist/tests/hook.test.js +62 -0
- package/dist/tests/hook.test.js.map +1 -0
- package/dist/tests/skeletonizer.test.d.ts +2 -0
- package/dist/tests/skeletonizer.test.d.ts.map +1 -0
- package/dist/tests/skeletonizer.test.js +71 -0
- package/dist/tests/skeletonizer.test.js.map +1 -0
- 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
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
[](https://pnpm.io/)
|
|
6
|
+
[-blue.svg)](https://www.typescriptlang.org/)
|
|
7
|
+
[-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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
+
}
|