ph-code-graph 0.5.0__tar.gz → 0.6.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.
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/PKG-INFO +8 -6
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/README.md +6 -4
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/pyproject.toml +2 -2
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/src/ph_code_graph/__init__.py +24 -22
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/tests/test_code_graph.py +20 -0
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/.gitignore +0 -0
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/LICENSE +0 -0
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/src/ph_code_graph/_extract.py +0 -0
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/src/ph_code_graph/_store.py +0 -0
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/src/ph_code_graph/bundle.yaml +0 -0
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/src/ph_code_graph/py.typed +0 -0
- {ph_code_graph-0.5.0 → ph_code_graph-0.6.0}/src/ph_code_graph/skills/code-graph/SKILL.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ph-code-graph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: pH plugin: `code_index` and `code_graph`, a Python-native tree-sitter code graph with line-accurate answers.
|
|
5
5
|
Project-URL: Homepage, https://github.com/chastabor/pH
|
|
6
6
|
Project-URL: Repository, https://github.com/chastabor/pH
|
|
@@ -21,7 +21,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
21
21
|
Classifier: Topic :: Software Development
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.12
|
|
24
|
-
Requires-Dist: ph-core==0.
|
|
24
|
+
Requires-Dist: ph-core==0.6.0
|
|
25
25
|
Requires-Dist: tree-sitter-language-pack>=1.16
|
|
26
26
|
Requires-Dist: tree-sitter>=0.25
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
@@ -206,10 +206,12 @@ would make `callers` return every place that merely *mentions* a type.
|
|
|
206
206
|
|
|
207
207
|
## Where things live
|
|
208
208
|
|
|
209
|
-
- **the index**: `$PH_CACHE/code-graph/<digest of the workspace root
|
|
210
|
-
cache root because it is rebuildable from the source, and keyed by root so
|
|
211
|
-
checkouts do not answer each other's questions.
|
|
212
|
-
|
|
209
|
+
- **the index**: `$PH_CACHE/code-graph/<digest of the workspace root>/graph.db` —
|
|
210
|
+
the cache root because it is rebuildable from the source, and keyed by root so
|
|
211
|
+
two checkouts do not answer each other's questions. The directory records its
|
|
212
|
+
workspace, and one whose workspace has been gone a week is removed on the next
|
|
213
|
+
index, so a profile that runs children in worktrees does not leave a database
|
|
214
|
+
per tree forever. `path:` in the row's config names one database instead.
|
|
213
215
|
- **the grammars**: `$PH_CACHE/tree-sitter`, set by the row at mount.
|
|
214
216
|
|
|
215
217
|
That second one is not housekeeping. The pack materializes even its *bundled*
|
|
@@ -178,10 +178,12 @@ would make `callers` return every place that merely *mentions* a type.
|
|
|
178
178
|
|
|
179
179
|
## Where things live
|
|
180
180
|
|
|
181
|
-
- **the index**: `$PH_CACHE/code-graph/<digest of the workspace root
|
|
182
|
-
cache root because it is rebuildable from the source, and keyed by root so
|
|
183
|
-
checkouts do not answer each other's questions.
|
|
184
|
-
|
|
181
|
+
- **the index**: `$PH_CACHE/code-graph/<digest of the workspace root>/graph.db` —
|
|
182
|
+
the cache root because it is rebuildable from the source, and keyed by root so
|
|
183
|
+
two checkouts do not answer each other's questions. The directory records its
|
|
184
|
+
workspace, and one whose workspace has been gone a week is removed on the next
|
|
185
|
+
index, so a profile that runs children in worktrees does not leave a database
|
|
186
|
+
per tree forever. `path:` in the row's config names one database instead.
|
|
185
187
|
- **the grammars**: `$PH_CACHE/tree-sitter`, set by the row at mount.
|
|
186
188
|
|
|
187
189
|
That second one is not housekeeping. The pack materializes even its *bundled*
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ph-code-graph"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
description = "pH plugin: `code_index` and `code_graph`, a Python-native tree-sitter code graph with line-accurate answers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -29,7 +29,7 @@ classifiers = [
|
|
|
29
29
|
# in (371 available, the rest fetched on demand — which this row never does
|
|
30
30
|
# implicitly; see `_extract.indexable`). No Rust toolchain, no Node, no
|
|
31
31
|
# submodule: the graph itself is stdlib `sqlite3`.
|
|
32
|
-
dependencies = ["ph-core==0.
|
|
32
|
+
dependencies = ["ph-core==0.6.0", "tree-sitter>=0.25", "tree-sitter-language-pack>=1.16"]
|
|
33
33
|
|
|
34
34
|
[project.urls]
|
|
35
35
|
Homepage = "https://github.com/chastabor/pH"
|
|
@@ -41,7 +41,6 @@ answers most of what an agent actually asks and reports where it cannot.
|
|
|
41
41
|
|
|
42
42
|
from __future__ import annotations
|
|
43
43
|
|
|
44
|
-
import hashlib
|
|
45
44
|
import importlib.util
|
|
46
45
|
import logging
|
|
47
46
|
from collections.abc import Mapping
|
|
@@ -59,7 +58,7 @@ from ph.llm.types import ContentBlock
|
|
|
59
58
|
from ph.paths import default_cache_path, resolve_roots
|
|
60
59
|
from ph.seams._registry import contribute_item
|
|
61
60
|
from ph.seams.changes import tree_state
|
|
62
|
-
from ph.seams.commands import
|
|
61
|
+
from ph.seams.commands import Provisioning, install_or_status
|
|
63
62
|
from ph.seams.diagnostics import Diagnostic, contribute
|
|
64
63
|
from ph.seams.fs import FsService
|
|
65
64
|
from ph.seams.skills import discover_skills
|
|
@@ -68,6 +67,7 @@ from ph.tools.definition import ToolModel, ToolOutput, ToolRunContext, define_to
|
|
|
68
67
|
from ph.tools.errors import HarnessError
|
|
69
68
|
from ph.tools.presentation import simple_views
|
|
70
69
|
from ph.wire import WireModel
|
|
70
|
+
from ph.workspace_caches import digest, use
|
|
71
71
|
|
|
72
72
|
from ._extract import (
|
|
73
73
|
cache_release,
|
|
@@ -127,7 +127,8 @@ class Config(WireModel):
|
|
|
127
127
|
"""Row config for `code-graph`."""
|
|
128
128
|
|
|
129
129
|
path: str = ""
|
|
130
|
-
"""Where the index lives. Defaults to `$PH_CACHE/code-graph/<root digest
|
|
130
|
+
"""Where the index lives. Defaults to `$PH_CACHE/code-graph/<root digest>/graph.db`;
|
|
131
|
+
a path set here is one database, used as given.
|
|
131
132
|
|
|
132
133
|
Under the cache root because the index is **rebuildable** — the source is the
|
|
133
134
|
truth and this is derived, which is the lifecycle `$PH_CACHE` names (Q1).
|
|
@@ -178,21 +179,22 @@ class CodeGraphSeam:
|
|
|
178
179
|
_lock: anyio.Lock = field(default_factory=anyio.Lock)
|
|
179
180
|
|
|
180
181
|
def store_for(self, root: Path) -> CodeGraphStore:
|
|
181
|
-
"""This workspace's index
|
|
182
|
+
"""This workspace's index: a directory per root, or the row's `path` as given.
|
|
182
183
|
|
|
183
184
|
Keyed per root on purpose — a worktree at another revision holds
|
|
184
185
|
different line numbers, so sharing one index would hand out pointers
|
|
185
186
|
that are quietly stale. The cost is that under a profile whose children
|
|
186
|
-
run in worktrees, every throwaway tree leaves a database behind,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
collects (`ph.seams.uploads` says the same about attachments): deleting
|
|
190
|
-
`$PH_CACHE/code-graph` reclaims all of them and costs a re-index.
|
|
187
|
+
run in worktrees, every throwaway tree leaves a database behind, so the
|
|
188
|
+
directory records its workspace and one whose workspace is gone is swept
|
|
189
|
+
on a later index (`ph.workspace_caches`).
|
|
191
190
|
"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
if self.config.path:
|
|
192
|
+
return CodeGraphStore(path=default_cache_path(self.config.path, "code-graph"))
|
|
193
|
+
return CodeGraphStore(path=self.base() / digest(str(root)) / "graph.db")
|
|
194
|
+
|
|
195
|
+
def base(self) -> Path:
|
|
196
|
+
"""Where the per-workspace indexes live, when no `path` names one database."""
|
|
197
|
+
return default_cache_path(None, "code-graph")
|
|
196
198
|
|
|
197
199
|
def locked(self) -> anyio.Lock:
|
|
198
200
|
return self._lock
|
|
@@ -560,6 +562,10 @@ async def apply(ctx: Context, config: Config) -> None:
|
|
|
560
562
|
skipped: list[dict[str, str]] = []
|
|
561
563
|
indexed = unchanged = symbols = removed = 0
|
|
562
564
|
|
|
565
|
+
if not config.path:
|
|
566
|
+
# Before the lock: the record and the sweep touch nothing an index
|
|
567
|
+
# holds. Not for a configured `path`, one database the row was given.
|
|
568
|
+
await anyio.to_thread.run_sync(use, seam.base(), fs.root_for(run.agent))
|
|
563
569
|
async with seam.locked():
|
|
564
570
|
await anyio.to_thread.run_sync(book.prepare)
|
|
565
571
|
if args.forget:
|
|
@@ -808,7 +814,7 @@ async def apply(ctx: Context, config: Config) -> None:
|
|
|
808
814
|
)
|
|
809
815
|
)
|
|
810
816
|
|
|
811
|
-
async def
|
|
817
|
+
async def grammar_command(verb: Provisioning) -> str:
|
|
812
818
|
"""`/code-graph install|status` — make the grammars ready, on purpose.
|
|
813
819
|
|
|
814
820
|
A **command** rather than a tool, per the seam's own rule: a person asks
|
|
@@ -817,9 +823,6 @@ async def apply(ctx: Context, config: Config) -> None:
|
|
|
817
823
|
the long tail fetches — but "is this ready" should have one answer per
|
|
818
824
|
plugin, asked the same way.
|
|
819
825
|
"""
|
|
820
|
-
verb = argument.strip().lower() or "status"
|
|
821
|
-
if verb not in ("install", "status"):
|
|
822
|
-
return f"/code-graph takes `install` or `status`, not {argument.strip()!r}."
|
|
823
826
|
wanted = [one for one in (config.languages or DEFAULT_LANGUAGES) if indexable(one)]
|
|
824
827
|
if verb == "status":
|
|
825
828
|
ready, missing = await anyio.to_thread.run_sync(readiness, wanted)
|
|
@@ -834,11 +837,10 @@ async def apply(ctx: Context, config: Config) -> None:
|
|
|
834
837
|
)
|
|
835
838
|
return f"all {count_of(len(ready), 'grammar')} ready under {seam.grammars}."
|
|
836
839
|
|
|
837
|
-
command =
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
run=install,
|
|
840
|
+
command = install_or_status(
|
|
841
|
+
"code-graph",
|
|
842
|
+
"Make the tree-sitter grammars ready, or report whether they are.",
|
|
843
|
+
grammar_command,
|
|
842
844
|
)
|
|
843
845
|
contribute_item(
|
|
844
846
|
ctx,
|
|
@@ -47,6 +47,7 @@ from ph.llm.types import text_of
|
|
|
47
47
|
from ph.testing import FAKE_OPTIONS, MountProfile, report_section, run_tool
|
|
48
48
|
from ph.testing.git import git, git_repo
|
|
49
49
|
from ph.testing.jj import jj_repo
|
|
50
|
+
from ph.workspace_caches import WORKSPACE_FILE
|
|
50
51
|
from ph_code_graph import CODE_GRAPH
|
|
51
52
|
from ph_code_graph._extract import (
|
|
52
53
|
INHERITS,
|
|
@@ -309,6 +310,25 @@ async def test_indexing_then_asking_answers_with_a_readable_pointer(
|
|
|
309
310
|
assert f"{hit['path']}:{hit['start_line']}" in text_of(found.content)
|
|
310
311
|
|
|
311
312
|
|
|
313
|
+
async def test_an_index_records_the_workspace_its_graph_serves(
|
|
314
|
+
mount: MountProfile, tmp_path: Path
|
|
315
|
+
) -> None:
|
|
316
|
+
"""What lets a sweep remove the graph of a worktree that is gone: its directory
|
|
317
|
+
names the workspace, and a digest cannot (`ph.workspace_caches`).
|
|
318
|
+
|
|
319
|
+
Sabotage: drop the `use` call from `code_index`, and nothing is recorded.
|
|
320
|
+
"""
|
|
321
|
+
_tree(tmp_path)
|
|
322
|
+
ctx = await mount(ROW)
|
|
323
|
+
root = ctx.require(FS).root_for(None)
|
|
324
|
+
|
|
325
|
+
built = await run_tool(ctx, "code_index", {"paths": ["pkg"]}, agent=_agent(ctx))
|
|
326
|
+
|
|
327
|
+
assert not built.is_error, text_of(built.content)
|
|
328
|
+
recorded = ctx.require(CODE_GRAPH).store_for(root).path.parent / WORKSPACE_FILE
|
|
329
|
+
assert recorded.read_text(encoding="utf-8") == str(root)
|
|
330
|
+
|
|
331
|
+
|
|
312
332
|
async def test_callers_names_the_calling_symbol_and_the_calling_line(
|
|
313
333
|
mount: MountProfile, tmp_path: Path
|
|
314
334
|
) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|