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 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
- - **AST Skeletonization & Slicing:** Folds inactive function bodies into signatures and windows targeted symbols (`-80% to -92%`).
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokensafe",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Autonomous Zero-Loss Token Economy & Context Optimizer for AI Coding Agents",
5
5
  "main": "bin/tokensafe.js",
6
6
  "bin": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agy-token-optimizer",
3
3
  "displayName": "TokenSafe",
4
- "version": "1.1.0",
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
@@ -2,5 +2,5 @@
2
2
  TokenSafe: Autonomous Zero-Loss Token Economy & Context Optimizer for AI Coding Agents.
3
3
  """
4
4
 
5
- __version__ = "1.1.0"
5
+ __version__ = "1.1.1"
6
6
  __author__ = "TokenSafe Team"
@@ -415,20 +415,48 @@ class CodeGraph:
415
415
 
416
416
 
417
417
  class GraphEngine:
418
- """Singleton Manager for automatic background graph execution."""
418
+ """Multi-Project Manager for automatic background graph execution."""
419
419
 
420
- _instance: Optional[CodeGraph] = None
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
- if cls._instance is None or cls._instance.root_dir != os.path.abspath(root_dir):
425
- cls._instance = CodeGraph(root_dir)
426
- cls._instance.scan()
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
- # Incremental scan if older than 3 seconds
429
- if time.time() - cls._instance.last_scanned > 3.0:
430
- cls._instance.scan()
431
- return cls._instance
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.0.0"
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():