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.
- witan_code/__init__.py +0 -0
- witan_code/__main__.py +4 -0
- witan_code/_detach.py +27 -0
- witan_code/bridge.py +392 -0
- witan_code/bridge_extractors.py +642 -0
- witan_code/cli.py +722 -0
- witan_code/config.py +65 -0
- witan_code/context.py +98 -0
- witan_code/edges.py +171 -0
- witan_code/elicit.py +92 -0
- witan_code/extensions/pi/codegraph.ts +121 -0
- witan_code/graph.py +271 -0
- witan_code/hooks.py +116 -0
- witan_code/indexer.py +1042 -0
- witan_code/maintenance.py +130 -0
- witan_code/package_map.py +74 -0
- witan_code/queries/bridge.gq +193 -0
- witan_code/queries/code_mutations.gq +81 -0
- witan_code/queries/code_read.gq +154 -0
- witan_code/queries/delete.gq +16 -0
- witan_code/queries_ts/bash.scm +9 -0
- witan_code/queries_ts/hcl.scm +16 -0
- witan_code/queries_ts/python.scm +29 -0
- witan_code/queries_ts/sql.scm +20 -0
- witan_code/queries_ts/typescript.scm +51 -0
- witan_code/queries_ts/yaml.scm +5 -0
- witan_code/repo.py +282 -0
- witan_code/schema/bridge-schema.pg +82 -0
- witan_code/schema/code-schema.pg +51 -0
- witan_code/server.py +859 -0
- witan_code/setup.py +230 -0
- witan_code/skills/witan-code/SKILL.md +97 -0
- witan_code/stitch.py +177 -0
- witan_code/store.py +116 -0
- witan_code/visualize.py +347 -0
- witan_code-0.2.0.dist-info/METADATA +476 -0
- witan_code-0.2.0.dist-info/RECORD +39 -0
- witan_code-0.2.0.dist-info/WHEEL +4 -0
- witan_code-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
; Python symbol extraction.
|
|
2
|
+
; Capture names drive Symbol kind + edge construction in indexer.py.
|
|
3
|
+
|
|
4
|
+
(function_definition
|
|
5
|
+
name: (identifier) @symbol.function)
|
|
6
|
+
|
|
7
|
+
(class_definition
|
|
8
|
+
name: (identifier) @symbol.class)
|
|
9
|
+
|
|
10
|
+
; Base classes — used for best-effort Inherits edges.
|
|
11
|
+
(class_definition
|
|
12
|
+
superclasses: (argument_list (identifier) @inherit.base))
|
|
13
|
+
|
|
14
|
+
; Imports — used for import-aware name resolution.
|
|
15
|
+
(import_statement
|
|
16
|
+
name: (dotted_name (identifier) @import.name))
|
|
17
|
+
|
|
18
|
+
(import_from_statement
|
|
19
|
+
name: (dotted_name (identifier) @import.name))
|
|
20
|
+
|
|
21
|
+
(import_from_statement
|
|
22
|
+
name: (aliased_import (dotted_name (identifier) @import.name)))
|
|
23
|
+
|
|
24
|
+
; Calls — best-effort Calls/References edges.
|
|
25
|
+
(call
|
|
26
|
+
function: (identifier) @call.name)
|
|
27
|
+
|
|
28
|
+
(call
|
|
29
|
+
function: (attribute attribute: (identifier) @call.name))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
; SQL symbol extraction.
|
|
2
|
+
;
|
|
3
|
+
; dbt-style models are usually a bare `select` wrapped in Jinja ({{ }} / {% %})
|
|
4
|
+
; that this grammar can't parse (each templated span becomes an isolated
|
|
5
|
+
; ERROR node; the surrounding SQL still parses). Those files rarely contain a
|
|
6
|
+
; CREATE statement — the CTEs inside the query are the only reliably
|
|
7
|
+
; navigable structure, so they're captured alongside the CREATE-statement
|
|
8
|
+
; definitions more common in migrations/raw SQL.
|
|
9
|
+
|
|
10
|
+
(create_table
|
|
11
|
+
(object_reference) @symbol.table)
|
|
12
|
+
|
|
13
|
+
(create_view
|
|
14
|
+
(object_reference) @symbol.table)
|
|
15
|
+
|
|
16
|
+
(create_function
|
|
17
|
+
(object_reference) @symbol.function)
|
|
18
|
+
|
|
19
|
+
(cte
|
|
20
|
+
(identifier) @symbol.cte)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
; TypeScript / JavaScript / JSX / TSX symbol extraction. All JS and TS variants
|
|
2
|
+
; are parsed with the tsx grammar (a superset), so this single query covers them.
|
|
3
|
+
|
|
4
|
+
(function_declaration
|
|
5
|
+
name: (identifier) @symbol.function)
|
|
6
|
+
|
|
7
|
+
(generator_function_declaration
|
|
8
|
+
name: (identifier) @symbol.function)
|
|
9
|
+
|
|
10
|
+
(class_declaration
|
|
11
|
+
name: (type_identifier) @symbol.class)
|
|
12
|
+
|
|
13
|
+
(method_definition
|
|
14
|
+
name: (property_identifier) @symbol.method)
|
|
15
|
+
|
|
16
|
+
; const Foo = () => {...} / const Foo = function () {...}
|
|
17
|
+
(variable_declarator
|
|
18
|
+
name: (identifier) @symbol.function
|
|
19
|
+
value: [(arrow_function) (function_expression)])
|
|
20
|
+
|
|
21
|
+
; class fields holding arrows: render = () => {...}
|
|
22
|
+
(public_field_definition
|
|
23
|
+
name: (property_identifier) @symbol.method
|
|
24
|
+
value: (arrow_function))
|
|
25
|
+
|
|
26
|
+
(interface_declaration
|
|
27
|
+
name: (type_identifier) @symbol.interface)
|
|
28
|
+
|
|
29
|
+
(type_alias_declaration
|
|
30
|
+
name: (type_identifier) @symbol.type)
|
|
31
|
+
|
|
32
|
+
(enum_declaration
|
|
33
|
+
name: (identifier) @symbol.enum)
|
|
34
|
+
|
|
35
|
+
; extends Base — best-effort Inherits.
|
|
36
|
+
(class_declaration
|
|
37
|
+
(class_heritage (extends_clause (identifier) @inherit.base)))
|
|
38
|
+
|
|
39
|
+
; imports — best-effort resolution.
|
|
40
|
+
(import_statement
|
|
41
|
+
(import_clause (identifier) @import.name))
|
|
42
|
+
|
|
43
|
+
(import_statement
|
|
44
|
+
(import_clause (named_imports (import_specifier name: (identifier) @import.name))))
|
|
45
|
+
|
|
46
|
+
; calls
|
|
47
|
+
(call_expression
|
|
48
|
+
function: (identifier) @call.name)
|
|
49
|
+
|
|
50
|
+
(call_expression
|
|
51
|
+
function: (member_expression property: (property_identifier) @call.name))
|
witan_code/repo.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def detect(override: str | None = None, start: Path | None = None) -> str | None:
|
|
9
|
+
"""
|
|
10
|
+
Return a canonical repo key (HTTPS URI) for ``start`` (or the cwd).
|
|
11
|
+
|
|
12
|
+
Resolution order:
|
|
13
|
+
1. ``override`` parameter (explicit caller value)
|
|
14
|
+
2. ``WITAN_REPO`` environment variable
|
|
15
|
+
3. ``git remote get-url origin`` (handles worktrees and multi-valued
|
|
16
|
+
config keys that ``configparser`` rejects)
|
|
17
|
+
4. ``origin`` remote URL parsed from the nearest ``.git/config``
|
|
18
|
+
5. directory name of the git root (fallback when no remote)
|
|
19
|
+
6. ``None`` — no repo context available
|
|
20
|
+
"""
|
|
21
|
+
if override is not None:
|
|
22
|
+
return override or None
|
|
23
|
+
|
|
24
|
+
if env_repo := os.environ.get("WITAN_REPO"):
|
|
25
|
+
return env_repo
|
|
26
|
+
|
|
27
|
+
base = start or Path.cwd()
|
|
28
|
+
|
|
29
|
+
if url := _git_origin(base):
|
|
30
|
+
return _normalise(url)
|
|
31
|
+
|
|
32
|
+
git_config_path = _find_git_config(base)
|
|
33
|
+
if git_config_path is None:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
if slug := _parse_origin(git_config_path):
|
|
37
|
+
return slug
|
|
38
|
+
|
|
39
|
+
# No origin remote — fall back to the repo root directory name.
|
|
40
|
+
return git_config_path.parent.parent.name
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _git_origin(start: Path) -> str | None:
|
|
44
|
+
"""Resolve the ``origin`` remote URL via git itself.
|
|
45
|
+
|
|
46
|
+
git is the only correct parser of its own config: it resolves git worktrees
|
|
47
|
+
(where ``.git`` is a file) and tolerates multi-valued keys (e.g. several
|
|
48
|
+
``fetch =`` lines) that Python's ``configparser`` rejects.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
result = subprocess.run(
|
|
52
|
+
["git", "-C", str(start), "remote", "get-url", "origin"],
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True,
|
|
55
|
+
check=False,
|
|
56
|
+
)
|
|
57
|
+
except OSError:
|
|
58
|
+
return None
|
|
59
|
+
if result.returncode != 0:
|
|
60
|
+
return None
|
|
61
|
+
return result.stdout.strip() or None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _find_git_config(start: Path) -> Path | None:
|
|
65
|
+
"""Walk up from ``start`` until a .git/config is found."""
|
|
66
|
+
for directory in [start, *start.parents]:
|
|
67
|
+
candidate = directory / ".git" / "config"
|
|
68
|
+
if candidate.exists():
|
|
69
|
+
return candidate
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _parse_origin(git_config: Path) -> str | None:
|
|
74
|
+
"""Parse .git/config and return the normalised ``origin`` remote URL."""
|
|
75
|
+
parser = configparser.RawConfigParser()
|
|
76
|
+
try:
|
|
77
|
+
parser.read(git_config)
|
|
78
|
+
except configparser.Error:
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
section = 'remote "origin"'
|
|
82
|
+
if not parser.has_option(section, "url"):
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
return _normalise(parser.get(section, "url"))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _normalise(url: str) -> str:
|
|
89
|
+
"""
|
|
90
|
+
Normalise a git remote URL to its canonical HTTPS project URI.
|
|
91
|
+
|
|
92
|
+
Must match ``witan/repo.py::_normalise`` so the repo key — used
|
|
93
|
+
in symbol ids (``repo#path::Name``) and the Layer-1 ``symbol_refs`` that point
|
|
94
|
+
at them — is identical across both stores.
|
|
95
|
+
|
|
96
|
+
Examples
|
|
97
|
+
--------
|
|
98
|
+
git@github.com:mitodl/ol-django.git → https://github.com/mitodl/ol-django
|
|
99
|
+
https://github.com/mitodl/ol-django → https://github.com/mitodl/ol-django
|
|
100
|
+
"""
|
|
101
|
+
url = re.sub(r"\.git$", "", url.strip()).rstrip("/")
|
|
102
|
+
|
|
103
|
+
if m := re.match(r"(?:ssh://)?[^@]+@([^:/]+)[:/](.+)", url):
|
|
104
|
+
return f"https://{m.group(1)}/{m.group(2)}"
|
|
105
|
+
|
|
106
|
+
if m := re.match(r"https?://(?:[^@/]+@)?([^/]+)/(.+)", url):
|
|
107
|
+
return f"https://{m.group(1)}/{m.group(2)}"
|
|
108
|
+
|
|
109
|
+
return url
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Omnigraph scratch branch for detached-HEAD checkouts: never index a detached
|
|
113
|
+
# state onto main (it would overwrite the shared view with an arbitrary commit).
|
|
114
|
+
DETACHED_BRANCH = "_detached"
|
|
115
|
+
|
|
116
|
+
_DEFAULT_BRANCHES = frozenset({"main", "master"})
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def sanitize_branch(name: str) -> str:
|
|
120
|
+
"""Make a git branch name safe as an omnigraph branch name."""
|
|
121
|
+
return re.sub(r"[^A-Za-z0-9._-]+", "_", name).strip("_") or DETACHED_BRANCH
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def branch_store_name(name: str) -> str:
|
|
125
|
+
"""Omnigraph branch name for a NON-DEFAULT git branch.
|
|
126
|
+
|
|
127
|
+
Omnigraph reserves ``main`` for the store's default branch, so a feature
|
|
128
|
+
branch literally named ``main`` (possible when the repo's default is
|
|
129
|
+
``master``) maps to ``_main``. sanitize_branch strips leading
|
|
130
|
+
underscores, so no other git branch can produce a ``_``-prefixed name —
|
|
131
|
+
the underscore namespace (``_main``, ``_detached``) is collision-free.
|
|
132
|
+
"""
|
|
133
|
+
sanitized = sanitize_branch(name)
|
|
134
|
+
return "_main" if sanitized == "main" else sanitized
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def current_branch(start: Path | None = None) -> str | None:
|
|
138
|
+
"""Current git branch name for ``start`` (or the cwd).
|
|
139
|
+
|
|
140
|
+
Returns ``"HEAD"`` for a detached checkout (git's own convention) and
|
|
141
|
+
``None`` outside a git repository or when git is unavailable.
|
|
142
|
+
"""
|
|
143
|
+
base = start or Path.cwd()
|
|
144
|
+
try:
|
|
145
|
+
result = subprocess.run(
|
|
146
|
+
["git", "-C", str(base), "rev-parse", "--abbrev-ref", "HEAD"],
|
|
147
|
+
capture_output=True,
|
|
148
|
+
text=True,
|
|
149
|
+
check=False,
|
|
150
|
+
)
|
|
151
|
+
except OSError:
|
|
152
|
+
return None
|
|
153
|
+
if result.returncode != 0:
|
|
154
|
+
return None
|
|
155
|
+
return result.stdout.strip() or None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _origin_default_branch(base: Path) -> str | None:
|
|
159
|
+
"""Short name of origin's HEAD branch (``main``), if resolvable locally."""
|
|
160
|
+
try:
|
|
161
|
+
result = subprocess.run(
|
|
162
|
+
[
|
|
163
|
+
"git",
|
|
164
|
+
"-C",
|
|
165
|
+
str(base),
|
|
166
|
+
"symbolic-ref",
|
|
167
|
+
"--short",
|
|
168
|
+
"refs/remotes/origin/HEAD",
|
|
169
|
+
],
|
|
170
|
+
capture_output=True,
|
|
171
|
+
text=True,
|
|
172
|
+
check=False,
|
|
173
|
+
)
|
|
174
|
+
except OSError:
|
|
175
|
+
return None
|
|
176
|
+
if result.returncode != 0:
|
|
177
|
+
return None
|
|
178
|
+
ref = result.stdout.strip()
|
|
179
|
+
return (ref.partition("/")[2] or None) if ref else None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _default_branch(base: Path) -> str | None:
|
|
183
|
+
"""The repo's default branch: origin HEAD, else main/master by presence.
|
|
184
|
+
|
|
185
|
+
The local fallback prefers ``main`` over ``master`` when both exist so
|
|
186
|
+
the choice is deterministic; whichever loses maps to its own store branch
|
|
187
|
+
(collision-free either way). Returns None when no default is
|
|
188
|
+
recognizable — then every branch gets its own store branch and nothing
|
|
189
|
+
claims the store's main.
|
|
190
|
+
"""
|
|
191
|
+
if default := _origin_default_branch(base):
|
|
192
|
+
return default
|
|
193
|
+
try:
|
|
194
|
+
result = subprocess.run(
|
|
195
|
+
["git", "-C", str(base), "branch", "--list", *_DEFAULT_BRANCHES],
|
|
196
|
+
capture_output=True,
|
|
197
|
+
text=True,
|
|
198
|
+
check=False,
|
|
199
|
+
)
|
|
200
|
+
except OSError:
|
|
201
|
+
return None
|
|
202
|
+
present = {line.lstrip("*+ ").strip() for line in result.stdout.splitlines()}
|
|
203
|
+
for name in ("main", "master"):
|
|
204
|
+
if name in present:
|
|
205
|
+
return name
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def store_branch(start: Path | None = None) -> str | None:
|
|
210
|
+
"""Omnigraph branch for the checkout at ``start``: ``None`` = store main.
|
|
211
|
+
|
|
212
|
+
The repo's default branch maps to the store's main branch; any other
|
|
213
|
+
branch maps through ``branch_store_name`` (so a non-default branch named
|
|
214
|
+
``main`` gets ``_main``, never colliding with the store's default); a
|
|
215
|
+
detached HEAD maps to the ``_detached`` scratch branch.
|
|
216
|
+
"""
|
|
217
|
+
base = start or Path.cwd()
|
|
218
|
+
branch = current_branch(base)
|
|
219
|
+
if branch is None:
|
|
220
|
+
return None
|
|
221
|
+
if branch == "HEAD":
|
|
222
|
+
return DETACHED_BRANCH
|
|
223
|
+
if branch == _default_branch(base):
|
|
224
|
+
return None
|
|
225
|
+
return branch_store_name(branch)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def local_branches(start: Path | None = None) -> frozenset[str] | None:
|
|
229
|
+
"""Store-branch names of all local git branches, or None when git fails.
|
|
230
|
+
|
|
231
|
+
Uses the same mapping as ``store_branch`` (``branch_store_name``) so
|
|
232
|
+
``branches --prune`` compares like with like — a git branch named
|
|
233
|
+
``main`` yields ``_main`` here, protecting its store branch from pruning.
|
|
234
|
+
"""
|
|
235
|
+
base = start or Path.cwd()
|
|
236
|
+
try:
|
|
237
|
+
result = subprocess.run(
|
|
238
|
+
[
|
|
239
|
+
"git",
|
|
240
|
+
"-C",
|
|
241
|
+
str(base),
|
|
242
|
+
"for-each-ref",
|
|
243
|
+
"--format=%(refname:short)",
|
|
244
|
+
"refs/heads",
|
|
245
|
+
],
|
|
246
|
+
capture_output=True,
|
|
247
|
+
text=True,
|
|
248
|
+
check=False,
|
|
249
|
+
)
|
|
250
|
+
except OSError:
|
|
251
|
+
return None
|
|
252
|
+
if result.returncode != 0:
|
|
253
|
+
return None
|
|
254
|
+
return frozenset(
|
|
255
|
+
branch_store_name(line) for line in result.stdout.splitlines() if line.strip()
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def root(start: Path | None = None) -> Path | None:
|
|
260
|
+
"""Return the git repository root for ``start`` (or the cwd).
|
|
261
|
+
|
|
262
|
+
Prefers ``git rev-parse --show-toplevel`` so worktrees and submodules (where
|
|
263
|
+
``.git`` is a file, not a directory) resolve correctly; falls back to walking
|
|
264
|
+
for ``.git/config`` when the git binary is unavailable.
|
|
265
|
+
"""
|
|
266
|
+
base = start or Path.cwd()
|
|
267
|
+
try:
|
|
268
|
+
result = subprocess.run(
|
|
269
|
+
["git", "-C", str(base), "rev-parse", "--show-toplevel"],
|
|
270
|
+
capture_output=True,
|
|
271
|
+
text=True,
|
|
272
|
+
check=False,
|
|
273
|
+
)
|
|
274
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
275
|
+
return Path(result.stdout.strip())
|
|
276
|
+
except OSError:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
git_config = _find_git_config(base)
|
|
280
|
+
if git_config is None:
|
|
281
|
+
return None
|
|
282
|
+
return git_config.parent.parent
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Cross-Repo Context Bridge (Layer 2.5) — shared LOCAL store (_bridge.omni).
|
|
2
|
+
//
|
|
3
|
+
// One flat node per interface binding. Cross-repo linkages are computed by
|
|
4
|
+
// GROUPING bindings on (kind, key_norm) where roles/repos differ — there are
|
|
5
|
+
// NO link edges. An anchor-node + edge model would force every repo touching a
|
|
6
|
+
// shared contract (e.g. env_var DATABASE_URL) to upsert the same node, maxing
|
|
7
|
+
// write contention on a store with many concurrent writers (every repo's index
|
|
8
|
+
// run + every PostToolUse reindex hook). Flat bindings are each scoped to their
|
|
9
|
+
// own repo+file, so concurrent writers never touch the same row.
|
|
10
|
+
//
|
|
11
|
+
// Local-only and re-derivable, like the per-repo Layer-2 stores; never synced.
|
|
12
|
+
//
|
|
13
|
+
// Id convention:
|
|
14
|
+
// InterfaceBinding.slug = repo|file|kind|key_norm|role|symbol_id
|
|
15
|
+
// e.g. https://github.com/mitodl/mit-learn|main/settings.py|env_var|
|
|
16
|
+
// MITOL_APP_BASE_URL|consumer|...settings.py::<module>
|
|
17
|
+
|
|
18
|
+
node InterfaceBinding {
|
|
19
|
+
slug: String @key
|
|
20
|
+
kind: enum(env_var, package, service, endpoint) @index
|
|
21
|
+
key: String @index // raw as written (METHOD path, pkg name, env NAME)
|
|
22
|
+
key_norm: String @index // normalized join key (collapsed path params, …)
|
|
23
|
+
role: enum(provider, consumer, shared) @index
|
|
24
|
+
repo: String @index // canonical HTTPS repo URI (join key to Layer 2)
|
|
25
|
+
file: String @index // repo-relative source path of the binding
|
|
26
|
+
repo_file: String @index // "repo|file" — single-field key for per-file delete
|
|
27
|
+
// (delete `where` takes one condition, no compound `and`)
|
|
28
|
+
sub_kind: String? // service anchor variant: repo | image | name
|
|
29
|
+
symbol_id: String? // enclosing Symbol id (repo#path::Qn) when applicable
|
|
30
|
+
line: I32?
|
|
31
|
+
language: String?
|
|
32
|
+
framework: String? // django | drf | pulumi | npm | nextjs | …
|
|
33
|
+
generic: String? // "1" for stoplisted generic keys (DEBUG, PORT, …)
|
|
34
|
+
confidence: F32? // 0.0–1.0 endpoint-consumer trust score (phantom
|
|
35
|
+
// suppression); null/absent treated as 1.0 by readers
|
|
36
|
+
symbol: String? @index // canonical symbol string (docs/SYMBOL_FORMAT.md):
|
|
37
|
+
// {scheme}:{manager}:{package}:{version}:{descriptor}
|
|
38
|
+
// Stage-2 precise join key; "."-fields = unresolved
|
|
39
|
+
indexed_at: DateTime
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Per-repo symbol table (Stage 1 artifact — docs/SYMBOL_TABLE.md): one row per
|
|
43
|
+
// (repo, role, symbol), aggregated from that repo's InterfaceBinding rows on
|
|
44
|
+
// every bridge write. role=exported is the repo's public contract surface;
|
|
45
|
+
// role=external is an unresolved reference to another repo's contract (a
|
|
46
|
+
// RANGER-style import placeholder, redirected at read time by Stage 2 — no
|
|
47
|
+
// cross-repo edges are ever written). Rows are repo-scoped, so concurrent
|
|
48
|
+
// writers never touch the same row (same argument as InterfaceBinding).
|
|
49
|
+
node RepoSymbol {
|
|
50
|
+
slug: String @key // repo|role|symbol
|
|
51
|
+
repo: String @index // canonical HTTPS repo URI
|
|
52
|
+
role: enum(exported, external) @index
|
|
53
|
+
symbol: String @index // full canonical string (docs/SYMBOL_FORMAT.md)
|
|
54
|
+
scheme: String @index // http | env | pkg | svc
|
|
55
|
+
descriptor: String @index // precise Stage-2 join key (with scheme)
|
|
56
|
+
key_norm: String @index // coarse join key — descriptor minus the
|
|
57
|
+
// method for http (consumer methods are "*")
|
|
58
|
+
manager: String? // "." = unknown
|
|
59
|
+
package: String? // "." = unresolved (typical for external)
|
|
60
|
+
version: String?
|
|
61
|
+
kind: String // binding kind: env_var | package | service | endpoint
|
|
62
|
+
n_refs: I32 // occurrence count in this repo
|
|
63
|
+
confidence: F32? // max occurrence confidence; 1.0 for exported
|
|
64
|
+
file: String? // exemplar occurrence (deterministic: min file/line)
|
|
65
|
+
line: I32?
|
|
66
|
+
indexed_at: DateTime
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// One row per indexed repo: its declared (or fallback) package identity from
|
|
70
|
+
// witan-code.toml (docs/PACKAGE_MAP.md). Overwritten on each full-repo index
|
|
71
|
+
// (merge by slug). Qualifies provider symbols and backs the
|
|
72
|
+
// known_provider_package confidence heuristic.
|
|
73
|
+
node PackageMap {
|
|
74
|
+
slug: String @key // canonical repo URI
|
|
75
|
+
repo: String @index
|
|
76
|
+
name: String @index // canonical package name
|
|
77
|
+
manager: String // pypi | npm | "."
|
|
78
|
+
version: String // "main" = trunk-tracking
|
|
79
|
+
provides: String? // JSON array of extra "manager:name" identities
|
|
80
|
+
declared: String? // "1" when read from witan-code.toml (vs fallback)
|
|
81
|
+
indexed_at: DateTime
|
|
82
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Code Graph (Layer 2) — per-repo tree-sitter-derived symbol graph.
|
|
2
|
+
//
|
|
3
|
+
// One store per repository, kept LOCAL only (never synced to team S3).
|
|
4
|
+
// Composes with witan (Layer 1) via soft symbol-ID references:
|
|
5
|
+
// Layer-1 nodes store symbol ids of the form `repo#path::Qualified.Name`,
|
|
6
|
+
// which resolve here without a hard cross-store edge.
|
|
7
|
+
//
|
|
8
|
+
// Id conventions:
|
|
9
|
+
// CodeFile.slug = repo#relative/path.py
|
|
10
|
+
// Symbol.slug = repo#relative/path.py::QualifiedName
|
|
11
|
+
// e.g. github.com/mitodl/repo#app/svc.py::Service.run
|
|
12
|
+
|
|
13
|
+
node CodeFile {
|
|
14
|
+
slug: String @key
|
|
15
|
+
repo: String @index
|
|
16
|
+
path: String @index
|
|
17
|
+
language: String @index
|
|
18
|
+
content_hash: String
|
|
19
|
+
indexed_at: DateTime
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
node Symbol {
|
|
23
|
+
slug: String @key
|
|
24
|
+
repo: String @index
|
|
25
|
+
file_id: String @index
|
|
26
|
+
name: String @index
|
|
27
|
+
qualified_name: String @index
|
|
28
|
+
kind: enum(function, method, class, module, variable, interface, type, enum, key, table, cte, block) @index
|
|
29
|
+
start_line: I32
|
|
30
|
+
end_line: I32
|
|
31
|
+
signature: String?
|
|
32
|
+
docstring: String?
|
|
33
|
+
decorators: [String]? // @decorators on the def (framework semantics: routes, tasks, fixtures, …)
|
|
34
|
+
indexed_at: DateTime
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Defines: a file defines a symbol (top-level or nested).
|
|
38
|
+
edge Defines: CodeFile -> Symbol
|
|
39
|
+
|
|
40
|
+
// Contains: lexical nesting (class Contains method, module Contains class).
|
|
41
|
+
edge Contains: Symbol -> Symbol
|
|
42
|
+
|
|
43
|
+
// Calls / References: HEURISTIC name-resolved usage edges (best-effort).
|
|
44
|
+
edge Calls: Symbol -> Symbol
|
|
45
|
+
edge References: Symbol -> Symbol
|
|
46
|
+
|
|
47
|
+
// Imports: a file imports a symbol (best-effort module/name resolution).
|
|
48
|
+
edge Imports: CodeFile -> Symbol
|
|
49
|
+
|
|
50
|
+
// Inherits: a class inherits from a base class (best-effort resolution).
|
|
51
|
+
edge Inherits: Symbol -> Symbol
|