tokensafe 1.1.0 → 1.1.1
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/README.md +28 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/src/__init__.py +1 -1
- package/src/graph_engine.py +37 -9
- package/src/mcp_server.py +52 -2
package/README.md
CHANGED
|
@@ -56,13 +56,34 @@ Traditional AI code interactions suffer from massive context bloat:
|
|
|
56
56
|
3. **Stuck Loops & Bloated Diffs:** Terminal `git diff` and repetitive failed commands spiral context into overflow.
|
|
57
57
|
|
|
58
58
|
### TokenSafe Solves All Three Autonomously:
|
|
59
|
+
- **Graph-Guided AST Skeletonization:** Folds inactive function bodies into signatures, keeping target functions and 1-hop dependencies full while collapsing unrelated code (`-85% to -92%`).
|
|
60
|
+
- **God Nodes & Architectural Hub Detection:** Calculates Degree Centrality to identify structural project hubs and injects topology awareness into agent sessions without expensive file crawling.
|
|
59
61
|
- **TOCP Prompt Distillation:** Intercepts prompts and distills intent into lean S-Expressions via **Google Gemini (2.0 Flash-Lite), OpenAI ChatGPT (gpt-4o-mini), or DeepSeek-Flash** (`-40% to -70%`). Automatically detects `GEMINI_API_KEY`, `OPENAI_API_KEY`, or `DEEPSEEK_API_KEY`.
|
|
60
|
-
- **
|
|
62
|
+
- **API Surface Map Generator:** Generates high-density `tokensafe-out/API_SURFACE.md` (~1.5k tokens) for instant agent codebase onboarding.
|
|
61
63
|
- **Predictive Dependency Prefetcher:** Attaches imported symbol signatures to eliminate downstream exploratory `read_file` roundtrips.
|
|
64
|
+
- **Local Secret Redactor:** Sanitizes Stripe, AWS, JWT, and database credentials before network transmission.
|
|
62
65
|
- **Output Guard & Loop Guard:** Blocks destructive full-file rewrites on files $\ge 40$ lines and halts command loops.
|
|
63
66
|
|
|
64
67
|
---
|
|
65
68
|
|
|
69
|
+
## ⚡ TokenSafe vs. Graphify: Dynamic In-Flight Defense vs. Static Reports
|
|
70
|
+
|
|
71
|
+
How TokenSafe complements or supersedes static graph tools like **Graphify** (`graphify-labs/graphify`):
|
|
72
|
+
|
|
73
|
+
| Capability | Graphify | TokenSafe |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| **Primary Focus** | Static knowledge graph reports & MCP queries | **In-flight real-time token compression & defense** |
|
|
76
|
+
| **Indexing Cost** | Expensive LLM tokens / heavy graph extraction | **0 tokens, 100% deterministic local AST (< 30ms)** |
|
|
77
|
+
| **Execution Latency** | Offline / CLI-initiated build | **Sub-millisecond inline proxy & lifecycle hooks** |
|
|
78
|
+
| **Active Slicing** | Static file output (`GRAPH_REPORT.md`) | **Dynamic call-graph skeletonization (85–92% saved)** |
|
|
79
|
+
| **Multi-Project Workspace** | Requires manual directory targeting | **Auto-detects project roots per file on the fly** |
|
|
80
|
+
| **Agent Prompt Optimization** | None | **TOCP S-Expression prompt distillation (-40% to -70%)** |
|
|
81
|
+
| **Safety & Runaway Prevention** | None | **Output rewrite guard, loop detector, secret scrubbing** |
|
|
82
|
+
|
|
83
|
+
> **Complements & Replaces**: If you already use Graphify for repository mapping, TokenSafe works alongside it by compressing real-time agent tool reads on the wire. Or use TokenSafe standalone for a zero-configuration, zero-cost, sub-30ms architecture engine out of the box.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
66
87
|
## 🤖 Universal LLM Distillation Providers
|
|
67
88
|
|
|
68
89
|
TokenSafe supports any key you already have:
|
|
@@ -110,6 +131,12 @@ Verified across 5 real-world production scenarios:
|
|
|
110
131
|
## 🛠️ CLI Quickstart
|
|
111
132
|
|
|
112
133
|
```bash
|
|
134
|
+
# Analyze repository God Nodes and dependency topology
|
|
135
|
+
tokensafe graph
|
|
136
|
+
|
|
137
|
+
# Generate compact tokensafe-out/API_SURFACE.md interface snapshot
|
|
138
|
+
tokensafe map
|
|
139
|
+
|
|
113
140
|
# View live savings dashboard in your terminal
|
|
114
141
|
tokensafe stats
|
|
115
142
|
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agy-token-optimizer",
|
|
3
3
|
"displayName": "TokenSafe",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.1",
|
|
5
5
|
"description": "Zero-Loss Token Economy & Context Optimizer for AI Agents. Compresses prompts, slices AST/tool outputs, and prevents stuck loops.",
|
|
6
6
|
"logo": "assets/logo.svg",
|
|
7
7
|
"author": {
|
package/src/__init__.py
CHANGED
package/src/graph_engine.py
CHANGED
|
@@ -415,20 +415,48 @@ class CodeGraph:
|
|
|
415
415
|
|
|
416
416
|
|
|
417
417
|
class GraphEngine:
|
|
418
|
-
"""
|
|
418
|
+
"""Multi-Project Manager for automatic background graph execution."""
|
|
419
419
|
|
|
420
|
-
|
|
420
|
+
_instances: Dict[str, CodeGraph] = {}
|
|
421
|
+
|
|
422
|
+
@classmethod
|
|
423
|
+
def find_project_root(cls, start_path: str) -> str:
|
|
424
|
+
"""Walks up from start_path to locate project boundary markers."""
|
|
425
|
+
curr = os.path.abspath(start_path)
|
|
426
|
+
if os.path.isfile(curr):
|
|
427
|
+
curr = os.path.dirname(curr)
|
|
428
|
+
|
|
429
|
+
root = os.path.abspath(os.sep)
|
|
430
|
+
while curr and curr != root:
|
|
431
|
+
for marker in (".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "requirements.txt"):
|
|
432
|
+
if os.path.exists(os.path.join(curr, marker)):
|
|
433
|
+
return curr
|
|
434
|
+
parent = os.path.dirname(curr)
|
|
435
|
+
if parent == curr:
|
|
436
|
+
break
|
|
437
|
+
curr = parent
|
|
438
|
+
return os.path.abspath(start_path if os.path.isdir(start_path) else os.path.dirname(start_path))
|
|
421
439
|
|
|
422
440
|
@classmethod
|
|
423
441
|
def get_instance(cls, root_dir: str = ".") -> CodeGraph:
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
442
|
+
root_abs = os.path.abspath(root_dir)
|
|
443
|
+
if root_abs not in cls._instances:
|
|
444
|
+
graph = CodeGraph(root_abs)
|
|
445
|
+
graph.scan()
|
|
446
|
+
cls._instances[root_abs] = graph
|
|
427
447
|
else:
|
|
428
|
-
|
|
429
|
-
if time.time() -
|
|
430
|
-
|
|
431
|
-
return
|
|
448
|
+
graph = cls._instances[root_abs]
|
|
449
|
+
if time.time() - graph.last_scanned > 3.0:
|
|
450
|
+
graph.scan()
|
|
451
|
+
return graph
|
|
452
|
+
|
|
453
|
+
@classmethod
|
|
454
|
+
def for_file(cls, file_path: str) -> Tuple[CodeGraph, str]:
|
|
455
|
+
"""Resolves the owning project graph and relative file path for any arbitrary file."""
|
|
456
|
+
root = cls.find_project_root(file_path)
|
|
457
|
+
graph = cls.get_instance(root)
|
|
458
|
+
rel_path = os.path.relpath(os.path.abspath(file_path), root).replace("\\", "/")
|
|
459
|
+
return graph, rel_path
|
|
432
460
|
|
|
433
461
|
def main():
|
|
434
462
|
import sys
|
package/src/mcp_server.py
CHANGED
|
@@ -21,10 +21,11 @@ from src.command_sanitizer import CommandSanitizer
|
|
|
21
21
|
from src.loop_detector import LoopDetector
|
|
22
22
|
from src.metrics import MetricsTracker
|
|
23
23
|
from src.dashboard import Dashboard
|
|
24
|
+
from src.graph_engine import GraphEngine
|
|
24
25
|
|
|
25
26
|
SERVER_INFO = {
|
|
26
27
|
"name": "tokensafe-mcp",
|
|
27
|
-
"version": "1.
|
|
28
|
+
"version": "1.1.1"
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
TOOLS = [
|
|
@@ -35,13 +36,33 @@ TOOLS = [
|
|
|
35
36
|
"type": "object",
|
|
36
37
|
"properties": {
|
|
37
38
|
"path": {"type": "string", "description": "Absolute path to the code file"},
|
|
38
|
-
"symbol": {"type": "string", "description": "Optional: targeted function or class name"},
|
|
39
|
+
"symbol": {"type": "string", "description": "Optional: targeted function or class name for graph-guided skeletonization"},
|
|
39
40
|
"start_line": {"type": "integer", "description": "Optional: starting line number"},
|
|
40
41
|
"end_line": {"type": "integer", "description": "Optional: ending line number"}
|
|
41
42
|
},
|
|
42
43
|
"required": ["path"]
|
|
43
44
|
}
|
|
44
45
|
},
|
|
46
|
+
{
|
|
47
|
+
"name": "get_architecture_graph",
|
|
48
|
+
"description": "Returns top architectural God Nodes and dependency topology for the target project.",
|
|
49
|
+
"inputSchema": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"project_path": {"type": "string", "description": "Optional: root directory or file path of the project"}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "get_api_surface",
|
|
58
|
+
"description": "Generates and returns a compact markdown map of all exported interfaces, classes, and types in the project.",
|
|
59
|
+
"inputSchema": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"project_path": {"type": "string", "description": "Optional: root directory or file path of the project"}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
45
66
|
{
|
|
46
67
|
"name": "compress_prompt",
|
|
47
68
|
"description": "Compresses a verbose developer prompt into an ultra-dense TOCP S-Expression via DeepSeek-V3 / Regex.",
|
|
@@ -102,6 +123,15 @@ def handle_tool_call(name: str, args: dict) -> dict:
|
|
|
102
123
|
return {"content": [{"type": "text", "text": (prefetch_block or "") + content}]}
|
|
103
124
|
|
|
104
125
|
if symbol:
|
|
126
|
+
try:
|
|
127
|
+
graph, rel_path = GraphEngine.for_file(path)
|
|
128
|
+
graph_skel = graph.graph_guided_skeletonize(path, target_symbol=symbol)
|
|
129
|
+
if graph_skel:
|
|
130
|
+
header = f"// [TokenSafe Graph-Guided Skeleton for '{symbol}' | Full body + 1-hop dependencies]\n"
|
|
131
|
+
return {"content": [{"type": "text", "text": (prefetch_block or "") + header + graph_skel}]}
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
|
|
105
135
|
overwrite, metrics = ToolSlicer.optimize_read_file({"Path": path}, context_hint=symbol)
|
|
106
136
|
if overwrite:
|
|
107
137
|
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
@@ -144,6 +174,26 @@ def handle_tool_call(name: str, args: dict) -> dict:
|
|
|
144
174
|
dashboard = Dashboard(tracker)
|
|
145
175
|
return {"content": [{"type": "text", "text": dashboard.render_markdown()}]}
|
|
146
176
|
|
|
177
|
+
elif name == "get_architecture_graph":
|
|
178
|
+
target = args.get("project_path") or os.getcwd()
|
|
179
|
+
graph = GraphEngine.get_instance(target)
|
|
180
|
+
god_nodes = graph.get_god_nodes(7)
|
|
181
|
+
banner = graph.format_topology_banner()
|
|
182
|
+
payload = {
|
|
183
|
+
"project_root": graph.root_dir,
|
|
184
|
+
"banner": banner,
|
|
185
|
+
"total_files": len(graph.file_symbols),
|
|
186
|
+
"total_symbols": len(graph.symbols),
|
|
187
|
+
"god_nodes": god_nodes
|
|
188
|
+
}
|
|
189
|
+
return {"content": [{"type": "text", "text": json.dumps(payload, indent=2, ensure_ascii=False)}]}
|
|
190
|
+
|
|
191
|
+
elif name == "get_api_surface":
|
|
192
|
+
target = args.get("project_path") or os.getcwd()
|
|
193
|
+
graph = GraphEngine.get_instance(target)
|
|
194
|
+
surface_md = graph.generate_api_surface()
|
|
195
|
+
return {"content": [{"type": "text", "text": surface_md}]}
|
|
196
|
+
|
|
147
197
|
return {"isError": True, "content": [{"type": "text", "text": f"Unknown tool: {name}"}]}
|
|
148
198
|
|
|
149
199
|
def run_mcp_server():
|