witan-code 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,130 @@
1
+ """Store compaction (omnigraph optimize/cleanup) with throttling.
2
+
3
+ Mirrors witan's own ``witan.maintenance`` (deliberately duplicated — no
4
+ cross-package import, see ``graph.py``'s docstring), adapted for witan-code's
5
+ per-repo, multi-store layout: there is no single configured store to
6
+ throttle. The current repo's per-repo store and the shared cross-repo bridge
7
+ store are each compacted and throttled independently, keyed by the store's
8
+ own path, so a bloated bridge store doesn't gate (or get gated by) a repo
9
+ store's compaction.
10
+
11
+ Every witan-code write — the ``PostToolUse`` single-file reindex, the
12
+ ``SessionStart`` full index — appends a new tiny Lance fragment + manifest
13
+ version to a store, and left uncompacted it bloats until *opening* the store
14
+ dominates query latency, the same failure mode witan's own store hit (#98).
15
+ ``omnigraph optimize`` collapses the fragments (non-destructive); ``cleanup``
16
+ GCs old versions to reclaim disk (destructive).
17
+
18
+ The ``Stop`` hook (``witan-code checkpoint``) calls
19
+ :func:`spawn_background_optimize` for whichever stores exist in the current
20
+ repo — at most once per interval each, detached so the hook returns
21
+ immediately. There is also a ``witan-code optimize`` / ``witan-code cleanup``
22
+ CLI for cron/systemd-timer driven maintenance.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import hashlib
28
+ import json
29
+ import os
30
+ import subprocess
31
+ import sys
32
+ import tempfile
33
+ import time
34
+ from pathlib import Path
35
+
36
+ from ._detach import popen_detached
37
+
38
+ # Opportunistic optimize runs at most once per this window per store. Optimize
39
+ # takes the store's write lock and is ~tens of seconds on a bloated store, so
40
+ # daily is a safe default; override with WITAN_CODE_OPTIMIZE_INTERVAL (seconds;
41
+ # 0 disables).
42
+ _OPTIMIZE_INTERVAL = 24 * 3600.0
43
+
44
+ _REMOTE_PREFIXES = ("http://", "https://", "s3://")
45
+
46
+
47
+ def optimize_interval() -> float:
48
+ """Throttle window in seconds; ``0`` (or negative) disables auto-optimize."""
49
+ raw = os.environ.get("WITAN_CODE_OPTIMIZE_INTERVAL")
50
+ if raw is None:
51
+ return _OPTIMIZE_INTERVAL
52
+ try:
53
+ return max(0.0, float(raw))
54
+ except ValueError:
55
+ return _OPTIMIZE_INTERVAL
56
+
57
+
58
+ def _stamp_file(store: str | Path) -> Path:
59
+ digest = hashlib.sha256(str(store).encode()).hexdigest()[:16]
60
+ return Path(tempfile.gettempdir()) / f"witan-code-optimize-{digest}.json"
61
+
62
+
63
+ def _last_run(store: str | Path) -> float:
64
+ try:
65
+ return float(json.loads(_stamp_file(store).read_text()).get("stamp", 0.0))
66
+ except Exception: # noqa: BLE001 — missing/corrupt stamp → treat as never run
67
+ return 0.0
68
+
69
+
70
+ def _mark_run(store: str | Path, when: float) -> None:
71
+ """Record the last-optimize time atomically.
72
+
73
+ Concurrent Stop hooks (or an interrupted write) could otherwise leave a
74
+ half-written stamp that ``_last_run`` reads as "never run", defeating the
75
+ throttle and letting optimize spawn repeatedly. Write a process-unique temp
76
+ file and ``os.replace`` it in, so a reader always sees a complete file.
77
+ """
78
+ path = _stamp_file(store)
79
+ tmp = path.with_name(f"{path.name}.{os.getpid()}.tmp")
80
+ try:
81
+ tmp.write_text(json.dumps({"stamp": when}))
82
+ os.replace(tmp, path)
83
+ except OSError:
84
+ try:
85
+ tmp.unlink(missing_ok=True)
86
+ except OSError:
87
+ pass
88
+
89
+
90
+ def due(store: str | Path, now: float | None = None) -> bool:
91
+ """Whether an opportunistic optimize is due for ``store``.
92
+
93
+ False when auto-optimize is disabled, the store is remote (maintained
94
+ server-side, not by a client hook), the store doesn't exist yet, or the
95
+ throttle window hasn't elapsed.
96
+ """
97
+ interval = optimize_interval()
98
+ if interval <= 0:
99
+ return False
100
+ if str(store).startswith(_REMOTE_PREFIXES):
101
+ return False
102
+ if not Path(store).exists():
103
+ return False
104
+ now = time.time() if now is None else now
105
+ return now - _last_run(store) >= interval
106
+
107
+
108
+ def spawn_background_optimize(store: str | Path, now: float | None = None) -> bool:
109
+ """If an optimize is due for ``store``, detach one and return ``True``.
110
+
111
+ Best-effort and non-blocking: the throttle stamp is written *before*
112
+ spawning (so a failing optimize can't hot-loop every session), and the
113
+ child is fully detached (its own session, no inherited stdio) so it
114
+ outlives the Stop hook. Never raises — a maintenance failure must not fail
115
+ the hook.
116
+ """
117
+ if not due(store, now):
118
+ return False
119
+ _mark_run(store, time.time() if now is None else now)
120
+ try:
121
+ popen_detached(
122
+ [sys.executable, "-m", "witan_code", "optimize", "--store", str(store)],
123
+ stdin=subprocess.DEVNULL,
124
+ stdout=subprocess.DEVNULL,
125
+ stderr=subprocess.DEVNULL,
126
+ env=dict(os.environ),
127
+ )
128
+ return True
129
+ except OSError:
130
+ return False
@@ -0,0 +1,74 @@
1
+ """Package map: a repo's declared canonical package identity.
2
+
3
+ Read from ``witan-code.toml`` at the repo root (see docs/PACKAGE_MAP.md).
4
+ The identity qualifies provider symbol strings and feeds the
5
+ ``known_provider_package`` confidence heuristic. Repos without the file get a
6
+ fallback identity derived from the repo URI so provider symbols always carry a
7
+ package qualifier.
8
+ """
9
+
10
+ import tomllib
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+
14
+ PACKAGE_MAP_FILENAME = "witan-code.toml"
15
+
16
+ # SCIP empty-field convention (see docs/SYMBOL_FORMAT.md).
17
+ EMPTY = "."
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class PackageIdentity:
22
+ name: str
23
+ manager: str = EMPTY
24
+ version: str = "main"
25
+ provides: tuple[str, ...] = field(default_factory=tuple)
26
+ """Extra published identities as ``"manager:name"`` strings."""
27
+
28
+ declared: bool = False
29
+ """True when read from witan-code.toml, False for a URI-derived fallback."""
30
+
31
+ def provided_names(self) -> frozenset[str]:
32
+ """Bare package names this repo provides (primary name + provides)."""
33
+ names = {self.name}
34
+ for entry in self.provides:
35
+ _, _, name = entry.partition(":")
36
+ names.add(name or entry)
37
+ return frozenset(names)
38
+
39
+
40
+ def fallback_identity(repo: str) -> PackageIdentity:
41
+ """Identity derived from the canonical repo URI when no TOML exists."""
42
+ name = repo.rstrip("/").rsplit("/", 1)[-1] or repo
43
+ return PackageIdentity(name=name)
44
+
45
+
46
+ def load(repo_root: Path, repo: str) -> PackageIdentity:
47
+ """Load ``witan-code.toml`` from ``repo_root``, else the fallback identity.
48
+
49
+ Malformed TOML or a missing ``[package].name`` degrades to the fallback —
50
+ the package map must never make indexing fail.
51
+ """
52
+ path = repo_root / PACKAGE_MAP_FILENAME
53
+ if not path.is_file():
54
+ return fallback_identity(repo)
55
+ try:
56
+ data = tomllib.loads(path.read_text())
57
+ except (tomllib.TOMLDecodeError, OSError, UnicodeDecodeError):
58
+ return fallback_identity(repo)
59
+
60
+ pkg = data.get("package")
61
+ if not isinstance(pkg, dict) or not isinstance(pkg.get("name"), str):
62
+ return fallback_identity(repo)
63
+
64
+ provides_raw = pkg.get("provides", ())
65
+ if not isinstance(provides_raw, (list, tuple)):
66
+ provides_raw = ()
67
+ provides = tuple(entry for entry in provides_raw if isinstance(entry, str))
68
+ return PackageIdentity(
69
+ name=pkg["name"],
70
+ manager=str(pkg.get("manager", EMPTY)),
71
+ version=str(pkg.get("version", "main")),
72
+ provides=provides,
73
+ declared=True,
74
+ )
@@ -0,0 +1,193 @@
1
+ // Cross-Repo Context Bridge — read / delete / search queries (_bridge.omni).
2
+ //
3
+ // Linkages are NOT edges: they are computed by grouping InterfaceBinding rows on
4
+ // (kind, key_norm) in Python. These queries just filter + search the flat node.
5
+ // Read filters use the match-shorthand `{ field: $param }` only (standalone
6
+ // `where`/quoted-key parse-errors in reads); deletes use `where`.
7
+
8
+ // ── Deletes (reindex cleanup) ─────────────────────────────────────
9
+ // Per-file purge: drop one source file's bindings so a reindex only replaces
10
+ // what it re-extracted (an incremental full-repo index skips unchanged files,
11
+ // so a repo-wide purge would orphan their bindings). Keyed on the composite
12
+ // repo_file ("repo|file") because delete `where` takes a single condition
13
+ // (no compound `and`).
14
+
15
+ query delete_bindings_in_file($repo_file: String) {
16
+ delete InterfaceBinding where repo_file = $repo_file
17
+ }
18
+
19
+ // All bindings — read once to build the cross-repo dependency visualization.
20
+
21
+ query all_bindings() {
22
+ match {
23
+ $b: InterfaceBinding
24
+ }
25
+ return {
26
+ $b.kind, $b.key, $b.key_norm, $b.role, $b.repo,
27
+ $b.file, $b.symbol_id, $b.framework, $b.generic, $b.confidence,
28
+ $b.symbol, $b.line
29
+ }
30
+ limit 1000000
31
+ }
32
+
33
+ // ── Per-repo symbol table (Stage 1 — docs/SYMBOL_TABLE.md) ────────
34
+ // The table is rebuilt exactly on every bridge write: delete the repo's rows,
35
+ // re-aggregate from surviving + fresh bindings. Delete is keyed on the single
36
+ // repo field (delete `where` takes one condition).
37
+
38
+ query delete_repo_symbols($repo: String) {
39
+ delete RepoSymbol where repo = $repo
40
+ }
41
+
42
+ query repo_symbols($repo: String) {
43
+ match {
44
+ $s: RepoSymbol { repo: $repo }
45
+ }
46
+ return {
47
+ $s.slug, $s.repo, $s.role, $s.symbol, $s.scheme, $s.descriptor,
48
+ $s.key_norm, $s.manager, $s.package, $s.version, $s.kind,
49
+ $s.n_refs, $s.confidence, $s.file, $s.line
50
+ }
51
+ order { $s.symbol asc }
52
+ limit 100000
53
+ }
54
+
55
+ // Every RepoSymbol row across all repos — Stage 2 (witan_code.stitch) reads
56
+ // this once and joins external/exported rows in Python, the same "bulk read,
57
+ // group in Python" pattern all_bindings/build_graph already use for the
58
+ // heuristic tier.
59
+
60
+ query all_repo_symbols() {
61
+ match {
62
+ $s: RepoSymbol
63
+ }
64
+ return {
65
+ $s.repo, $s.role, $s.symbol, $s.scheme, $s.descriptor,
66
+ $s.key_norm, $s.manager, $s.package, $s.version, $s.kind,
67
+ $s.n_refs, $s.confidence, $s.file, $s.line
68
+ }
69
+ limit 1000000
70
+ }
71
+
72
+ // Stage-2 join primitives: exact descriptor match (env/svc), and the key_norm
73
+ // match (http/pkg). pkg canonical descriptors are always "." (SYMBOL_FORMAT.md
74
+ // — package identity lives in the manager/package fields, not the
75
+ // descriptor), so symbols_by_descriptor cannot distinguish packages;
76
+ // key_norm carries the package name for both exported and external rows
77
+ // instead. http also joins on key_norm rather than descriptor because the
78
+ // descriptor embeds the method, which consumers usually can't determine
79
+ // statically ("*").
80
+
81
+ query symbols_by_descriptor($scheme: String, $descriptor: String) {
82
+ match {
83
+ $s: RepoSymbol { scheme: $scheme, descriptor: $descriptor }
84
+ }
85
+ return {
86
+ $s.slug, $s.repo, $s.role, $s.symbol, $s.scheme, $s.descriptor,
87
+ $s.key_norm, $s.manager, $s.package, $s.version, $s.kind,
88
+ $s.n_refs, $s.confidence, $s.file, $s.line
89
+ }
90
+ order { $s.repo asc }
91
+ limit 1000
92
+ }
93
+
94
+ query symbols_by_key($scheme: String, $key_norm: String) {
95
+ match {
96
+ $s: RepoSymbol { scheme: $scheme, key_norm: $key_norm }
97
+ }
98
+ return {
99
+ $s.slug, $s.repo, $s.role, $s.symbol, $s.scheme, $s.descriptor,
100
+ $s.key_norm, $s.manager, $s.package, $s.version, $s.kind,
101
+ $s.n_refs, $s.confidence, $s.file, $s.line
102
+ }
103
+ order { $s.repo asc }
104
+ limit 1000
105
+ }
106
+
107
+ // ── Package maps ──────────────────────────────────────────────────
108
+ // Every repo's declared/fallback package identity (docs/PACKAGE_MAP.md).
109
+ // Read at bridge-write time to qualify symbols and feed the
110
+ // known_provider_package heuristic.
111
+
112
+ query all_package_maps() {
113
+ match {
114
+ $p: PackageMap
115
+ }
116
+ return {
117
+ $p.repo, $p.name, $p.manager, $p.version, $p.provides, $p.declared
118
+ }
119
+ limit 10000
120
+ }
121
+
122
+ // ── Grouping reads ────────────────────────────────────────────────
123
+ // All bindings for a contract key (the workhorse behind providers / consumers /
124
+ // impact grouping).
125
+
126
+ query bindings_by_key($kind: String, $key_norm: String) {
127
+ match {
128
+ $b: InterfaceBinding { kind: $kind, key_norm: $key_norm }
129
+ }
130
+ return {
131
+ $b.slug, $b.kind, $b.key, $b.key_norm, $b.role, $b.repo,
132
+ $b.file, $b.symbol_id, $b.line, $b.language, $b.framework, $b.generic,
133
+ $b.symbol
134
+ }
135
+ order { $b.repo asc }
136
+ limit 500
137
+ }
138
+
139
+ // Same filtered to one role — backs code_interface_providers / _consumers.
140
+
141
+ query bindings_by_key_role($kind: String, $key_norm: String, $role: String) {
142
+ match {
143
+ $b: InterfaceBinding { kind: $kind, key_norm: $key_norm, role: $role }
144
+ }
145
+ return {
146
+ $b.slug, $b.kind, $b.key, $b.key_norm, $b.role, $b.repo,
147
+ $b.file, $b.symbol_id, $b.line, $b.language, $b.framework, $b.generic,
148
+ $b.symbol
149
+ }
150
+ order { $b.repo asc }
151
+ limit 500
152
+ }
153
+
154
+ // Bindings attributed to a symbol — first half of code_cross_repo_impact.
155
+
156
+ query bindings_for_symbol($symbol_id: String) {
157
+ match {
158
+ $b: InterfaceBinding { symbol_id: $symbol_id }
159
+ }
160
+ return {
161
+ $b.slug, $b.kind, $b.key, $b.key_norm, $b.role, $b.repo,
162
+ $b.file, $b.symbol_id, $b.generic
163
+ }
164
+ limit 500
165
+ }
166
+
167
+ // ── Full-text search (BM25 on key_norm) ───────────────────────────
168
+
169
+ query search_bindings($query: String) {
170
+ match {
171
+ $b: InterfaceBinding
172
+ search($b.key_norm, $query)
173
+ }
174
+ return {
175
+ $b.slug, $b.kind, $b.key, $b.key_norm, $b.role, $b.repo,
176
+ $b.file, $b.symbol_id, $b.framework, $b.generic
177
+ }
178
+ order { bm25($b.key_norm, $query) desc }
179
+ limit 100
180
+ }
181
+
182
+ query search_bindings_by_kind($query: String, $kind: String) {
183
+ match {
184
+ $b: InterfaceBinding { kind: $kind }
185
+ search($b.key_norm, $query)
186
+ }
187
+ return {
188
+ $b.slug, $b.kind, $b.key, $b.key_norm, $b.role, $b.repo,
189
+ $b.file, $b.symbol_id, $b.framework, $b.generic
190
+ }
191
+ order { bm25($b.key_norm, $query) desc }
192
+ limit 100
193
+ }
@@ -0,0 +1,81 @@
1
+ // Code Graph — insert/update mutations.
2
+ //
3
+ // NOTE: Omnigraph cannot mix deletes with inserts/updates in one query.
4
+ // Deletes for reindexing live in delete.gq and are run as separate change()
5
+ // calls BEFORE these inserts.
6
+
7
+ // ── CodeFile ──────────────────────────────────────────────────────
8
+
9
+ query insert_file(
10
+ $id: String,
11
+ $repo: String,
12
+ $path: String,
13
+ $language: String,
14
+ $content_hash: String,
15
+ $indexed_at: DateTime
16
+ ) {
17
+ insert CodeFile {
18
+ slug: $id,
19
+ repo: $repo,
20
+ path: $path,
21
+ language: $language,
22
+ content_hash: $content_hash,
23
+ indexed_at: $indexed_at
24
+ }
25
+ }
26
+
27
+ // ── Symbol ────────────────────────────────────────────────────────
28
+
29
+ query insert_symbol(
30
+ $id: String,
31
+ $repo: String,
32
+ $file_id: String,
33
+ $name: String,
34
+ $qualified_name: String,
35
+ $kind: String,
36
+ $start_line: I32,
37
+ $end_line: I32,
38
+ $signature: String?,
39
+ $docstring: String?,
40
+ $indexed_at: DateTime
41
+ ) {
42
+ insert Symbol {
43
+ slug: $id,
44
+ repo: $repo,
45
+ file_id: $file_id,
46
+ name: $name,
47
+ qualified_name: $qualified_name,
48
+ kind: $kind,
49
+ start_line: $start_line,
50
+ end_line: $end_line,
51
+ signature: $signature,
52
+ docstring: $docstring,
53
+ indexed_at: $indexed_at
54
+ }
55
+ }
56
+
57
+ // ── Edges ─────────────────────────────────────────────────────────
58
+
59
+ query link_defines($from: String, $to: String) {
60
+ insert Defines { from: $from, to: $to }
61
+ }
62
+
63
+ query link_contains($from: String, $to: String) {
64
+ insert Contains { from: $from, to: $to }
65
+ }
66
+
67
+ query link_calls($from: String, $to: String) {
68
+ insert Calls { from: $from, to: $to }
69
+ }
70
+
71
+ query link_references($from: String, $to: String) {
72
+ insert References { from: $from, to: $to }
73
+ }
74
+
75
+ query link_imports($from: String, $to: String) {
76
+ insert Imports { from: $from, to: $to }
77
+ }
78
+
79
+ query link_inherits($from: String, $to: String) {
80
+ insert Inherits { from: $from, to: $to }
81
+ }
@@ -0,0 +1,154 @@
1
+ // Code Graph — read queries.
2
+ //
3
+ // Dispatch strategy mirrors witan: the MCP server picks the named
4
+ // query based on the call. Symbol ids are `<repo>#<path/to/file.py>::<Name>`.
5
+ // Queries project the id as `slug`; the server renames it to `symbol_id`
6
+ // (see server._as_symbol_id) so it round-trips under the tools' param name.
7
+
8
+ // ── Definition lookup ─────────────────────────────────────────────
9
+
10
+ query find_by_name($name: String) {
11
+ match {
12
+ $s: Symbol { name: $name }
13
+ }
14
+ return {
15
+ $s.slug, $s.repo, $s.file_id, $s.name, $s.qualified_name,
16
+ $s.kind, $s.start_line, $s.end_line, $s.signature, $s.docstring,
17
+ $s.decorators
18
+ }
19
+ order { $s.qualified_name asc }
20
+ limit 50
21
+ }
22
+
23
+ query find_by_qualified_name($qualified_name: String) {
24
+ match {
25
+ $s: Symbol { qualified_name: $qualified_name }
26
+ }
27
+ return {
28
+ $s.slug, $s.repo, $s.file_id, $s.name, $s.qualified_name,
29
+ $s.kind, $s.start_line, $s.end_line, $s.signature, $s.docstring,
30
+ $s.decorators
31
+ }
32
+ order { $s.qualified_name asc }
33
+ limit 50
34
+ }
35
+
36
+ query get_symbol($id: String) {
37
+ match {
38
+ $s: Symbol { slug: $id }
39
+ }
40
+ return {
41
+ $s.slug, $s.repo, $s.file_id, $s.name, $s.qualified_name,
42
+ $s.kind, $s.start_line, $s.end_line, $s.signature, $s.docstring,
43
+ $s.decorators
44
+ }
45
+ }
46
+
47
+ // ── Full-text symbol search (BM25 on name / qualified_name) ────────
48
+
49
+ query search_symbols($query: String) {
50
+ match {
51
+ $s: Symbol
52
+ search($s.qualified_name, $query)
53
+ }
54
+ return {
55
+ $s.slug, $s.repo, $s.file_id, $s.name, $s.qualified_name,
56
+ $s.kind, $s.start_line, $s.end_line, $s.signature
57
+ }
58
+ order { bm25($s.qualified_name, $query) desc }
59
+ limit 50
60
+ }
61
+
62
+ query search_symbols_by_kind($query: String, $kind: String) {
63
+ match {
64
+ $s: Symbol { kind: $kind }
65
+ search($s.qualified_name, $query)
66
+ }
67
+ return {
68
+ $s.slug, $s.repo, $s.file_id, $s.name, $s.qualified_name,
69
+ $s.kind, $s.start_line, $s.end_line, $s.signature
70
+ }
71
+ order { bm25($s.qualified_name, $query) desc }
72
+ limit 50
73
+ }
74
+
75
+ // ── File symbols ──────────────────────────────────────────────────
76
+
77
+ query symbols_in_file($file_id: String) {
78
+ match {
79
+ $s: Symbol { file_id: $file_id }
80
+ }
81
+ return {
82
+ $s.slug, $s.name, $s.qualified_name, $s.kind,
83
+ $s.start_line, $s.end_line, $s.signature, $s.docstring, $s.decorators
84
+ }
85
+ order { $s.start_line asc }
86
+ }
87
+
88
+ // ── Edge traversal ────────────────────────────────────────────────
89
+ // Incoming Calls: symbols that call $id.
90
+
91
+ query callers($id: String) {
92
+ match {
93
+ $target: Symbol { slug: $id }
94
+ $caller: Symbol
95
+ $caller calls $target
96
+ }
97
+ return {
98
+ $caller.slug, $caller.repo, $caller.file_id, $caller.name,
99
+ $caller.qualified_name, $caller.kind,
100
+ $caller.start_line, $caller.end_line
101
+ }
102
+ order { $caller.qualified_name asc }
103
+ limit 200
104
+ }
105
+
106
+ // Incoming Calls + References: anything that uses $id.
107
+
108
+ query referencers($id: String) {
109
+ match {
110
+ $target: Symbol { slug: $id }
111
+ $src: Symbol
112
+ $src references $target
113
+ }
114
+ return {
115
+ $src.slug, $src.repo, $src.file_id, $src.name,
116
+ $src.qualified_name, $src.kind,
117
+ $src.start_line, $src.end_line
118
+ }
119
+ order { $src.qualified_name asc }
120
+ limit 200
121
+ }
122
+
123
+ // ── CodeFile fetch (for incremental reindex) ──────────────────────
124
+
125
+ query get_file($id: String) {
126
+ match {
127
+ $f: CodeFile { slug: $id }
128
+ }
129
+ return {
130
+ $f.slug, $f.repo, $f.path, $f.language,
131
+ $f.content_hash, $f.indexed_at
132
+ }
133
+ }
134
+
135
+ // All file hashes — read once per index run so the incremental skip check is
136
+ // in-memory rather than a query per file.
137
+
138
+ query all_file_hashes() {
139
+ match {
140
+ $f: CodeFile
141
+ }
142
+ return { $f.slug, $f.content_hash }
143
+ limit 1000000
144
+ }
145
+
146
+ // Symbol ids defined by a file — needed to delete their outgoing edges
147
+ // before reindexing the file.
148
+
149
+ query symbol_ids_in_file($file_id: String) {
150
+ match {
151
+ $s: Symbol { file_id: $file_id }
152
+ }
153
+ return { $s.slug }
154
+ }
@@ -0,0 +1,16 @@
1
+ // Code Graph — delete queries (reindex path).
2
+ //
3
+ // Omnigraph cannot mix deletes with inserts/updates in one query, so these
4
+ // run as their own change() calls BEFORE re-inserting a file's data.
5
+ //
6
+ // Edges are deleted by deleting their endpoint nodes: removing a Symbol or
7
+ // CodeFile removes its incident edges. We delete all Symbols belonging to a
8
+ // file (by file_id) and then the CodeFile itself.
9
+
10
+ query delete_symbols_in_file($file_id: String) {
11
+ delete Symbol where file_id = $file_id
12
+ }
13
+
14
+ query delete_file($id: String) {
15
+ delete CodeFile where slug = $id
16
+ }
@@ -0,0 +1,9 @@
1
+ ; Bash / Zsh symbol extraction. The tree-sitter bash grammar covers most zsh.
2
+
3
+ ; function foo() { ... } and foo() { ... }
4
+ (function_definition
5
+ name: (word) @symbol.function)
6
+
7
+ ; calls — best-effort Calls/References edges to other functions.
8
+ (command
9
+ name: (command_name (word) @call.name))
@@ -0,0 +1,16 @@
1
+ ; HCL symbol extraction (Terraform / Packer / Vault-policy style configs).
2
+ ;
3
+ ; Each labeled block (resource/variable/output/module/data/source/path, …)
4
+ ; becomes a Symbol keyed on its LAST label — the part that's actually unique
5
+ ; (`resource "aws_instance" "foo"` -> foo, not the shared "aws_instance" type;
6
+ ; `variable "app_name"` -> app_name; Vault's `path "secret/data/*"` -> the
7
+ ; path itself). The `.` anchor requires the label be immediately followed by
8
+ ; the opening brace, which is only true of the last label when there are two.
9
+ ;
10
+ ; Unlabeled blocks (locals, bare provider blocks) aren't captured — there's
11
+ ; no unique text to key them on.
12
+
13
+ (block
14
+ (string_lit) @symbol.block
15
+ .
16
+ (block_start))