contextl 1.2.11 → 1.2.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contextl",
3
- "version": "1.2.11",
3
+ "version": "1.2.12",
4
4
  "description": "contextl — finds the most relevant files in your codebase for any change request. MCP server for AI coding agents.",
5
5
  "keywords": [
6
6
  "mcp",
package/python/main.py CHANGED
@@ -198,9 +198,9 @@ def build_parser() -> argparse.ArgumentParser:
198
198
  search_parser.add_argument("--top", "-n", type=int, default=5, help="Number of results to return (default: 5)")
199
199
  search_parser.add_argument("--json", action="store_true", help="Output clean JSON instead of human-readable text")
200
200
 
201
- # 2. Dead Code
202
- dead_parser = subparsers.add_parser("dead-code", help="Find files with 0 in-degree dependencies")
203
- dead_parser.add_argument("repo_path", help="Path to the repository root")
201
+ # 2. Standalone Files
202
+ standalone_parser = subparsers.add_parser("standalone", help="Find files with 0 in-degree dependencies (unimported)")
203
+ standalone_parser.add_argument("repo_path", help="Path to the repository root")
204
204
 
205
205
  # 3. Impact Analysis
206
206
  impact_parser = subparsers.add_parser("impact", help="Analyze the impact of changing a file")
@@ -218,7 +218,7 @@ def build_parser() -> argparse.ArgumentParser:
218
218
  def main():
219
219
  # To maintain backward compatibility with old `contextl <repo> <query>`
220
220
  # we manually inject "search" if the first argument isn't a known command.
221
- if len(sys.argv) >= 2 and sys.argv[1] not in ["search", "dead-code", "impact", "obsidian", "-h", "--help"]:
221
+ if len(sys.argv) >= 2 and sys.argv[1] not in ["search", "standalone", "impact", "obsidian", "-h", "--help"]:
222
222
  sys.argv.insert(1, "search")
223
223
 
224
224
  parser = build_parser()
@@ -243,14 +243,14 @@ def main():
243
243
  else:
244
244
  print(_format_human(args.query, args.repo_path, results, repo_graph, elapsed))
245
245
 
246
- elif args.command == "dead-code":
247
- from dead_code import find_dead_files
246
+ elif args.command == "standalone":
247
+ from standalone import find_standalone_files
248
248
  scan = scan_repo(args.repo_path)
249
249
  parse = parse_imports(scan)
250
250
  repo_graph = build_graph(scan, parse)
251
- dead = find_dead_files(repo_graph)
252
- print(f"Found {len(dead)} potentially dead files:")
253
- for d in sorted(dead):
251
+ standalone = find_standalone_files(repo_graph)
252
+ print(f"Found {len(standalone)} standalone files:")
253
+ for d in sorted(standalone):
254
254
  print(f" {d}")
255
255
 
256
256
  elif args.command == "impact":
@@ -35,7 +35,7 @@ from graph_builder import build_graph
35
35
  from query_engine import query as engine_query
36
36
  from main import _confidence, _reasoning
37
37
  from impact_analysis import analyze_impact
38
- from dead_code import find_dead_files
38
+ from standalone import find_standalone_files
39
39
  from obsidian_export import export_obsidian_vault
40
40
 
41
41
 
@@ -145,11 +145,11 @@ async def list_tools() -> list[types.Tool]:
145
145
  },
146
146
  ),
147
147
  types.Tool(
148
- name="find_dead_files",
148
+ name="find_standalone_files",
149
149
  description=(
150
150
  "Find files in the repository that are never imported by any other file "
151
151
  "(in-degree of 0 in the dependency graph). Automatically filters out "
152
- "standard entry points and test files. Use this to identify dead code "
152
+ "standard entry points and test files. Use this to identify standalone code "
153
153
  "and unused files that can potentially be deleted."
154
154
  ),
155
155
  inputSchema={
@@ -203,8 +203,8 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
203
203
  if name == "analyze_impact":
204
204
  return await _handle_impact(arguments)
205
205
 
206
- if name == "find_dead_files":
207
- return await _handle_dead_files(arguments)
206
+ if name == "find_standalone_files":
207
+ return await _handle_standalone_files(arguments)
208
208
 
209
209
  if name == "export_obsidian_vault":
210
210
  return await _handle_export_obsidian(arguments)
@@ -321,30 +321,30 @@ def _run_impact(repo_path: str, target_file: str, max_depth: int) -> dict:
321
321
  }
322
322
 
323
323
 
324
- async def _handle_dead_files(args: dict) -> list[types.TextContent]:
324
+ async def _handle_standalone_files(args: dict) -> list[types.TextContent]:
325
325
  repo_path = args.get("repo_path", "")
326
326
 
327
327
  loop = asyncio.get_event_loop()
328
328
  try:
329
- result = await loop.run_in_executor(None, _run_dead_files, repo_path)
329
+ result = await loop.run_in_executor(None, _run_standalone_files, repo_path)
330
330
  except Exception as e:
331
331
  result = {"error": str(e), "repo": repo_path}
332
332
 
333
333
  return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
334
334
 
335
335
 
336
- def _run_dead_files(repo_path: str) -> dict:
336
+ def _run_standalone_files(repo_path: str) -> dict:
337
337
  """Synchronous pipeline — called from a thread executor."""
338
338
  scan = scan_repo(repo_path)
339
339
  parse = parse_imports(scan)
340
340
  repo_graph = build_graph(scan, parse)
341
- dead_files = find_dead_files(repo_graph)
341
+ standalone_files = find_standalone_files(repo_graph)
342
342
 
343
343
  return {
344
344
  "repo": scan.root,
345
345
  "total_files_scanned": scan.total_files,
346
- "total_dead_files": len(dead_files),
347
- "dead_files": dead_files
346
+ "total_standalone_files": len(standalone_files),
347
+ "standalone_files": standalone_files
348
348
  }
349
349
 
350
350
 
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Repository Intelligence Engine
3
- Step 7: Dead Code Analysis
3
+ Step 7: Standalone Files Analysis
4
4
 
5
5
  Finds files in the dependency graph that have an in-degree of 0
6
6
  (meaning they are never imported by any other file), while filtering
@@ -19,29 +19,33 @@ ENTRY_POINT_MARKERS = {
19
19
  "globals.css", "global.css", "tailwind.css",
20
20
  "index.ts", "index.js", "index.tsx", "index.jsx",
21
21
  "main.ts", "main.js", "main.py",
22
+ "app.ts", "app.js", "app.tsx", "app.jsx", "app.py",
23
+ "mcp_server.py", "server.ts", "server.js", "server.py",
22
24
  "__main__.py", "__init__.py", "setup.py", "conftest.py",
23
25
  "next.config", "tailwind.config", "postcss.config", "vite.config", "webpack.config"
24
26
  }
25
27
 
26
28
  def _is_entry_point(path: str) -> bool:
27
29
  lowered = path.lower()
30
+ if lowered.endswith(".d.ts"):
31
+ return True
28
32
  for marker in ENTRY_POINT_MARKERS:
29
33
  if marker in lowered:
30
34
  return True
31
35
  return False
32
36
 
33
- def find_dead_files(repo_graph: RepoGraph) -> list[str]:
37
+ def find_standalone_files(repo_graph: RepoGraph) -> list[str]:
34
38
  """
35
39
  Find files with in-degree 0, excluding entry points and tests.
36
40
  """
37
- dead_files = []
41
+ standalone_files = []
38
42
 
39
43
  for path, node in repo_graph.nodes.items():
40
44
  if node.in_degree == 0:
41
45
  if not _is_test_file(path) and not _is_entry_point(path):
42
- dead_files.append(path)
46
+ standalone_files.append(path)
43
47
 
44
- return sorted(dead_files)
48
+ return sorted(standalone_files)
45
49
 
46
50
  if __name__ == "__main__":
47
51
  import sys
@@ -50,7 +54,7 @@ if __name__ == "__main__":
50
54
  from graph_builder import build_graph
51
55
 
52
56
  if len(sys.argv) < 2:
53
- print("Usage: python dead_code.py <repo_path>")
57
+ print("Usage: python standalone.py <repo_path>")
54
58
  sys.exit(1)
55
59
 
56
60
  repo_path = sys.argv[1]
@@ -58,8 +62,8 @@ if __name__ == "__main__":
58
62
  parse = parse_imports(scan)
59
63
  repo_graph = build_graph(scan, parse)
60
64
 
61
- dead = find_dead_files(repo_graph)
65
+ standalone = find_standalone_files(repo_graph)
62
66
 
63
- print(f"Found {len(dead)} potentially dead files:")
64
- for f in dead:
67
+ print(f"Found {len(standalone)} standalone files:")
68
+ for f in standalone:
65
69
  print(f" - {f}")