minder-cli 0.3.8__tar.gz → 0.4.0__tar.gz

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 (145) hide show
  1. {minder_cli-0.3.8 → minder_cli-0.4.0}/PKG-INFO +1 -1
  2. {minder_cli-0.3.8 → minder_cli-0.4.0}/pyproject.toml +1 -1
  3. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/commands/update.py +7 -7
  4. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/utils/version.py +15 -2
  5. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/repo_scanner.py +38 -41
  6. {minder_cli-0.3.8 → minder_cli-0.4.0}/.gitignore +0 -0
  7. {minder_cli-0.3.8 → minder_cli-0.4.0}/LICENSE +0 -0
  8. {minder_cli-0.3.8 → minder_cli-0.4.0}/README-pypi.md +0 -0
  9. {minder_cli-0.3.8 → minder_cli-0.4.0}/README.md +0 -0
  10. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/__init__.py +0 -0
  11. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/api/routers/prompts.py +0 -0
  12. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/application/__init__.py +0 -0
  13. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/application/admin/__init__.py +0 -0
  14. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/application/admin/dto.py +0 -0
  15. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/application/admin/jobs.py +0 -0
  16. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/application/admin/use_cases.py +0 -0
  17. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/auth/__init__.py +0 -0
  18. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/auth/context.py +0 -0
  19. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/auth/middleware.py +0 -0
  20. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/auth/principal.py +0 -0
  21. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/auth/rate_limiter.py +0 -0
  22. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/auth/rbac.py +0 -0
  23. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/auth/service.py +0 -0
  24. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/bootstrap/__init__.py +0 -0
  25. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/bootstrap/providers.py +0 -0
  26. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/bootstrap/transport.py +0 -0
  27. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/cache/__init__.py +0 -0
  28. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/cache/providers.py +0 -0
  29. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/chunking/__init__.py +0 -0
  30. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/chunking/code_splitter.py +0 -0
  31. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/chunking/splitter.py +0 -0
  32. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/cli.py +0 -0
  33. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/config.py +0 -0
  34. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/continuity.py +0 -0
  35. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/dev.py +0 -0
  36. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/embedding/__init__.py +0 -0
  37. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/embedding/base.py +0 -0
  38. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/embedding/local.py +0 -0
  39. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/embedding/openai.py +0 -0
  40. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/__init__.py +0 -0
  41. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/edges.py +0 -0
  42. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/executor.py +0 -0
  43. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/graph.py +0 -0
  44. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/__init__.py +0 -0
  45. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/evaluator.py +0 -0
  46. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/guard.py +0 -0
  47. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/llm.py +0 -0
  48. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/planning.py +0 -0
  49. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/reasoning.py +0 -0
  50. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/reranker.py +0 -0
  51. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/retriever.py +0 -0
  52. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/verification.py +0 -0
  53. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/nodes/workflow_planner.py +0 -0
  54. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/runtime.py +0 -0
  55. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/graph/state.py +0 -0
  56. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/llm/__init__.py +0 -0
  57. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/llm/base.py +0 -0
  58. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/llm/factory.py +0 -0
  59. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/llm/litert.py +0 -0
  60. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/llm/openai.py +0 -0
  61. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/__init__.py +0 -0
  62. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/base.py +0 -0
  63. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/client.py +0 -0
  64. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/document.py +0 -0
  65. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/error.py +0 -0
  66. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/graph.py +0 -0
  67. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/history.py +0 -0
  68. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/job.py +0 -0
  69. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/prompt.py +0 -0
  70. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/repository.py +0 -0
  71. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/rule.py +0 -0
  72. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/session.py +0 -0
  73. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/skill.py +0 -0
  74. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/user.py +0 -0
  75. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/models/workflow.py +0 -0
  76. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/observability/__init__.py +0 -0
  77. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/observability/audit.py +0 -0
  78. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/observability/logging.py +0 -0
  79. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/observability/metrics.py +0 -0
  80. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/observability/tracing.py +0 -0
  81. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/__init__.py +0 -0
  82. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/__init__.py +0 -0
  83. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/commands/auth.py +0 -0
  84. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/commands/ide.py +0 -0
  85. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/commands/mcp.py +0 -0
  86. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/commands/sync.py +0 -0
  87. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/main.py +0 -0
  88. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/utils/common.py +0 -0
  89. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/utils/config.py +0 -0
  90. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/cli/utils/git.py +0 -0
  91. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/__init__.py +0 -0
  92. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/__init__.py +0 -0
  93. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/api.py +0 -0
  94. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/context.py +0 -0
  95. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/dashboard.py +0 -0
  96. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/jobs.py +0 -0
  97. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/memories.py +0 -0
  98. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/prompts.py +0 -0
  99. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/routes.py +0 -0
  100. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/runtime.py +0 -0
  101. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/search.py +0 -0
  102. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/presentation/http/admin/skills.py +0 -0
  103. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/prompts/__init__.py +0 -0
  104. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/prompts/formatter.py +0 -0
  105. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/resources/__init__.py +0 -0
  106. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/retrieval/__init__.py +0 -0
  107. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/retrieval/hybrid.py +0 -0
  108. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/retrieval/mmr.py +0 -0
  109. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/retrieval/multi_hop.py +0 -0
  110. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/runtime.py +0 -0
  111. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/server.py +0 -0
  112. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/__init__.py +0 -0
  113. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/document.py +0 -0
  114. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/error.py +0 -0
  115. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/feedback.py +0 -0
  116. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/graph.py +0 -0
  117. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/history.py +0 -0
  118. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/interfaces.py +0 -0
  119. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/milvus/__init__.py +0 -0
  120. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/milvus/client.py +0 -0
  121. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/milvus/collections.py +0 -0
  122. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/milvus/vector_store.py +0 -0
  123. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/mongodb/__init__.py +0 -0
  124. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/mongodb/client.py +0 -0
  125. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/mongodb/indexes.py +0 -0
  126. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/mongodb/operational_store.py +0 -0
  127. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/relational.py +0 -0
  128. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/repo_state.py +0 -0
  129. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/rule.py +0 -0
  130. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/store/vector.py +0 -0
  131. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/__init__.py +0 -0
  132. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/auth.py +0 -0
  133. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/graph.py +0 -0
  134. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/ingest.py +0 -0
  135. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/memory.py +0 -0
  136. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/query.py +0 -0
  137. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/registry.py +0 -0
  138. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/search.py +0 -0
  139. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/session.py +0 -0
  140. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/skills.py +0 -0
  141. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/tools/workflow.py +0 -0
  142. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/transport/__init__.py +0 -0
  143. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/transport/base.py +0 -0
  144. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/transport/sse.py +0 -0
  145. {minder_cli-0.3.8 → minder_cli-0.4.0}/src/minder/transport/stdio.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: minder-cli
3
- Version: 0.3.8
3
+ Version: 0.4.0
4
4
  Summary: Minder CLI is the command-line interface for the Minder self-hosted MCP platform.
5
5
  Project-URL: Homepage, https://github.com/hiimtrung/minder
6
6
  Project-URL: Repository, https://github.com/hiimtrung/minder
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "minder-cli"
7
- version = "0.3.8"
7
+ version = "0.4.0"
8
8
  description = "Minder CLI is the command-line interface for the Minder self-hosted MCP platform."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
@@ -35,7 +35,7 @@ def version_command(args: argparse.Namespace) -> int:
35
35
  else:
36
36
  print(" status: up to date")
37
37
  else:
38
- print("minder version unknown (not installed as a package)")
38
+ print("minder-cli version unknown (not installed as a package)")
39
39
  return 0
40
40
 
41
41
 
@@ -109,15 +109,15 @@ def check_update_command(args: argparse.Namespace) -> int:
109
109
 
110
110
  def _cli_update_commands(manager: str) -> list[list[str]]:
111
111
  if manager == "uv":
112
- return [["uv", "tool", "upgrade", "minder"]]
112
+ return [["uv", "tool", "upgrade", "minder-cli"]]
113
113
  if manager == "pipx":
114
- return [["pipx", "upgrade", "minder"]]
114
+ return [["pipx", "upgrade", "minder-cli"]]
115
115
  if manager == "pip":
116
- return [[sys.executable, "-m", "pip", "install", "--upgrade", "minder"]]
116
+ return [[sys.executable, "-m", "pip", "install", "--upgrade", "minder-cli"]]
117
117
  return [
118
- ["uv", "tool", "upgrade", "minder"],
119
- ["pipx", "upgrade", "minder"],
120
- [sys.executable, "-m", "pip", "install", "--upgrade", "minder"],
118
+ ["uv", "tool", "upgrade", "minder-cli"],
119
+ ["pipx", "upgrade", "minder-cli"],
120
+ [sys.executable, "-m", "pip", "install", "--upgrade", "minder-cli"],
121
121
  ]
122
122
 
123
123
 
@@ -6,11 +6,12 @@ from importlib.metadata import (
6
6
  version as package_version,
7
7
  )
8
8
 
9
+ from pathlib import Path
9
10
  import httpx
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
13
- _PYPI_JSON_URL = "https://pypi.org/pypi/minder/json"
14
+ _PYPI_JSON_URL = "https://pypi.org/pypi/minder-cli/json"
14
15
 
15
16
 
16
17
  def parse_version(raw_version: str) -> tuple[int, ...]:
@@ -26,8 +27,20 @@ def parse_version(raw_version: str) -> tuple[int, ...]:
26
27
 
27
28
  def installed_package_version() -> str | None:
28
29
  try:
29
- return package_version("minder")
30
+ return package_version("minder-cli")
30
31
  except PackageNotFoundError:
32
+ # Fallback for development mode: try to read from pyproject.toml
33
+ try:
34
+ # Look for pyproject.toml in the repo root (5 levels up from this file)
35
+ pyproject_path = Path(__file__).parents[5] / "pyproject.toml"
36
+ if pyproject_path.exists():
37
+ content = pyproject_path.read_text()
38
+ import re
39
+ match = re.search(r'^version\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE)
40
+ if match:
41
+ return match.group(1) + "-dev"
42
+ except Exception:
43
+ pass
31
44
  return None
32
45
 
33
46
 
@@ -132,6 +132,7 @@ class RepoScanner:
132
132
  self._git_metadata_cache: dict[str, dict[str, Any]] = {}
133
133
  self._git_line_commit_cache: dict[tuple[str, int], dict[str, str] | None] = {}
134
134
  self._git_commit_detail_cache: dict[str, dict[str, str]] = {}
135
+ self._git_file_blame_cache: dict[str, dict[int, str]] = {}
135
136
  self._git_enabled = True
136
137
 
137
138
  def _run_git(
@@ -280,6 +281,7 @@ class RepoScanner:
280
281
  builder._git_metadata_cache = {}
281
282
  builder._git_line_commit_cache = {}
282
283
  builder._git_commit_detail_cache = {}
284
+ builder._git_file_blame_cache = {}
283
285
  builder._git_enabled = True
284
286
 
285
287
  service_dirs = builder._discover_service_boundaries()
@@ -374,24 +376,21 @@ class RepoScanner:
374
376
  }
375
377
 
376
378
  def _discover_service_boundaries(self) -> list[Path]:
377
- service_dirs: list[Path] = []
378
- for marker in _SERVICE_MARKERS:
379
- for marker_path in self._root.rglob(marker):
380
- if any(part.startswith(".") for part in marker_path.parts):
381
- continue
382
- svc_dir = marker_path.parent
383
- if svc_dir not in service_dirs:
384
- service_dirs.append(svc_dir)
385
- return sorted(service_dirs, key=lambda path: len(path.parts), reverse=True)
379
+ service_dirs: set[Path] = set()
380
+ for path in self._root.rglob("*"):
381
+ if any(part.startswith(".") for part in path.parts):
382
+ continue
383
+ if path.is_file() and path.name in _SERVICE_MARKERS:
384
+ service_dirs.add(path.parent)
385
+ return sorted(list(service_dirs), key=lambda path: len(path.parts), reverse=True)
386
386
 
387
387
  def _discover_source_files(self) -> list[Path]:
388
388
  files: list[Path] = []
389
- for suffix in _SOURCE_SUFFIXES:
390
- for path in self._root.rglob(f"*{suffix}"):
391
- if any(part.startswith(".") or part == "__pycache__" for part in path.parts):
392
- continue
393
- if path.is_file():
394
- files.append(path)
389
+ for path in self._root.rglob("*"):
390
+ if any(part.startswith(".") or part == "__pycache__" for part in path.parts):
391
+ continue
392
+ if path.is_file() and path.suffix.lower() in _SOURCE_SUFFIXES:
393
+ files.append(path)
395
394
  return sorted(set(files))
396
395
 
397
396
  def _resolve_source_files(self, changed_files: list[str] | None) -> list[Path]:
@@ -579,34 +578,32 @@ class RepoScanner:
579
578
  return commits
580
579
 
581
580
  def _git_line_commit(self, rel_path: str, line_number: int) -> dict[str, str] | None:
582
- cache_key = (rel_path, line_number)
583
- if cache_key in self._git_line_commit_cache:
584
- return self._git_line_commit_cache[cache_key]
585
-
586
- result = self._run_git(
587
- [
588
- "blame",
589
- "--line-porcelain",
590
- "-L",
591
- f"{line_number},{line_number}",
592
- "--",
593
- rel_path,
594
- ],
595
- check=False,
596
- )
597
- if result.returncode != 0:
598
- self._git_line_commit_cache[cache_key] = None
599
- return None
600
-
601
- first_line = next((line for line in result.stdout.splitlines() if line.strip()), "")
602
- sha = first_line.split(" ", 1)[0].strip()
603
- if not sha or set(sha) == {"0"}:
604
- self._git_line_commit_cache[cache_key] = None
581
+ if rel_path not in self._git_file_blame_cache:
582
+ result = self._run_git(["blame", "--line-porcelain", "--", rel_path], check=False)
583
+ if result.returncode != 0:
584
+ self._git_file_blame_cache[rel_path] = {}
585
+ else:
586
+ blame_data: dict[int, str] = {}
587
+ current_sha = None
588
+ for line in result.stdout.splitlines():
589
+ if not line:
590
+ continue
591
+ if current_sha is None:
592
+ current_sha = line.split(" ", 1)[0]
593
+ elif line.startswith("\t"):
594
+ # This line indicates the end of a porcelain block
595
+ current_sha = None
596
+ elif line.startswith("result-line "):
597
+ target_line = int(line.split(" ")[1])
598
+ if current_sha and set(current_sha) != {"0"}:
599
+ blame_data[target_line] = current_sha
600
+ self._git_file_blame_cache[rel_path] = blame_data
601
+
602
+ sha = self._git_file_blame_cache.get(rel_path, {}).get(line_number)
603
+ if not sha:
605
604
  return None
606
605
 
607
- details = self._git_commit_details(sha)
608
- self._git_line_commit_cache[cache_key] = details
609
- return details
606
+ return self._git_commit_details(sha)
610
607
 
611
608
  def _git_commit_details(self, sha: str) -> dict[str, str]:
612
609
  cached = self._git_commit_detail_cache.get(sha)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes