feed-the-machine 1.3.1 → 1.4.0

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 (32) hide show
  1. package/ftm-git/SKILL.md +0 -1
  2. package/ftm-map/SKILL.md +46 -14
  3. package/ftm-map/scripts/db.py +439 -118
  4. package/ftm-map/scripts/index.py +128 -54
  5. package/ftm-map/scripts/parser.py +89 -320
  6. package/ftm-map/scripts/queries/go-tags.scm +20 -0
  7. package/ftm-map/scripts/queries/javascript-tags.scm +19 -7
  8. package/ftm-map/scripts/queries/python-tags.scm +22 -8
  9. package/ftm-map/scripts/queries/ruby-tags.scm +19 -0
  10. package/ftm-map/scripts/queries/rust-tags.scm +37 -0
  11. package/ftm-map/scripts/queries/typescript-tags.scm +20 -8
  12. package/ftm-map/scripts/query.py +176 -24
  13. package/ftm-map/scripts/ranker.py +377 -0
  14. package/ftm-map/scripts/requirements.txt +3 -0
  15. package/ftm-map/scripts/setup.sh +11 -0
  16. package/ftm-map/scripts/test_db.py +355 -115
  17. package/ftm-map/scripts/test_parser.py +169 -101
  18. package/ftm-map/scripts/test_query.py +178 -61
  19. package/ftm-map/scripts/test_ranker.py +199 -0
  20. package/ftm-map/scripts/views.py +107 -61
  21. package/ftm-mind/references/event-registry.md +0 -10
  22. package/hooks/ftm-blackboard-enforcer.sh +1 -4
  23. package/package.json +1 -1
  24. package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
  25. package/ftm-inbox/backend/planner/__pycache__/__init__.cpython-314.pyc +0 -0
  26. package/ftm-inbox/backend/planner/__pycache__/generator.cpython-314.pyc +0 -0
  27. package/ftm-inbox/backend/planner/__pycache__/schema.cpython-314.pyc +0 -0
  28. package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
  29. package/ftm-map/scripts/tests/fixtures/__init__.py +0 -0
  30. package/ftm-map/scripts/tests/fixtures/sample_project/api.ts +0 -16
  31. package/ftm-map/scripts/tests/fixtures/sample_project/auth.py +0 -15
  32. package/ftm-map/scripts/tests/fixtures/sample_project/utils.js +0 -16
@@ -1,106 +1,174 @@
1
- """Tests for ftm-map parser module."""
1
+ """Tests for parser.py -- Aider-style def/ref tag extraction."""
2
2
  import os
3
3
  import sys
4
- import unittest
4
+ import pytest
5
5
 
6
6
  sys.path.insert(0, os.path.dirname(__file__))
7
+ from parser import get_tags, detect_language, Tag, EXTENSION_MAP
7
8
 
8
- FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "tests", "fixtures", "sample_project")
9
-
10
- class TestParser(unittest.TestCase):
11
-
12
- @classmethod
13
- def setUpClass(cls):
14
- """Check if tree-sitter-language-pack is available."""
15
- try:
16
- from parser import parse_file, extract_relationships, detect_language
17
- cls.parse_file = staticmethod(parse_file)
18
- cls.extract_relationships = staticmethod(extract_relationships)
19
- cls.detect_language = staticmethod(detect_language)
20
- cls.skip_reason = None
21
- except ImportError as e:
22
- cls.skip_reason = f"tree-sitter-language-pack not installed: {e}"
23
-
24
- def setUp(self):
25
- if self.skip_reason:
26
- self.skipTest(self.skip_reason)
27
-
28
- def test_detect_language(self):
29
- self.assertEqual(self.detect_language("foo.py"), "python")
30
- self.assertEqual(self.detect_language("bar.ts"), "typescript")
31
- self.assertEqual(self.detect_language("baz.js"), "javascript")
32
- self.assertIsNone(self.detect_language("README.md"))
33
-
34
- def test_parse_python_file(self):
35
- """Should extract Python functions."""
36
- path = os.path.join(FIXTURES_DIR, "auth.py")
37
- if not os.path.exists(path):
38
- self.skipTest("Fixture not found")
39
- symbols = self.parse_file(path)
40
- names = {s.name for s in symbols}
41
- self.assertIn("handleAuth", names)
42
- self.assertIn("validateToken", names)
43
- self.assertIn("getUser", names)
44
-
45
- def test_parse_typescript_file(self):
46
- """Should extract TypeScript functions and classes."""
47
- path = os.path.join(FIXTURES_DIR, "api.ts")
48
- if not os.path.exists(path):
49
- self.skipTest("Fixture not found")
50
- symbols = self.parse_file(path)
51
- names = {s.name for s in symbols}
52
- self.assertIn("processRequest", names)
53
- self.assertIn("ApiController", names)
54
-
55
- def test_parse_javascript_file(self):
56
- """Should extract JavaScript functions."""
57
- path = os.path.join(FIXTURES_DIR, "utils.js")
58
- if not os.path.exists(path):
59
- self.skipTest("Fixture not found")
60
- symbols = self.parse_file(path)
61
- names = {s.name for s in symbols}
62
- self.assertIn("formatDate", names)
63
- self.assertIn("parseConfig", names)
64
-
65
- def test_symbol_has_content_hash(self):
66
- """Every symbol should have a content hash."""
67
- path = os.path.join(FIXTURES_DIR, "auth.py")
68
- if not os.path.exists(path):
69
- self.skipTest("Fixture not found")
70
- symbols = self.parse_file(path)
71
- for sym in symbols:
72
- self.assertTrue(len(sym.content_hash) > 0, f"{sym.name} missing content_hash")
73
-
74
- def test_symbol_has_line_numbers(self):
75
- """Every symbol should have start and end line."""
76
- path = os.path.join(FIXTURES_DIR, "auth.py")
77
- if not os.path.exists(path):
78
- self.skipTest("Fixture not found")
79
- symbols = self.parse_file(path)
80
- for sym in symbols:
81
- self.assertGreater(sym.start_line, 0)
82
- self.assertGreaterEqual(sym.end_line, sym.start_line)
83
-
84
- def test_extract_relationships(self):
85
- """Should extract call relationships."""
86
- path = os.path.join(FIXTURES_DIR, "auth.py")
87
- if not os.path.exists(path):
88
- self.skipTest("Fixture not found")
89
- rels = self.extract_relationships(path)
90
- # handleAuth calls validateToken and getUser
91
- call_targets = {r.target_name for r in rels if r.kind == "calls"}
92
- self.assertIn("validateToken", call_targets)
93
- self.assertIn("getUser", call_targets)
94
-
95
- def test_unsupported_file_returns_empty(self):
96
- """Unsupported file types should return empty list."""
97
- symbols = self.parse_file("/tmp/fake.xyz")
98
- self.assertEqual(symbols, [])
99
-
100
- def test_nonexistent_file_returns_empty(self):
101
- """Non-existent files should return empty list, not error."""
102
- symbols = self.parse_file("/tmp/nonexistent_file_12345.py")
103
- self.assertEqual(symbols, [])
104
-
105
- if __name__ == "__main__":
106
- unittest.main()
9
+ FIXTURES = os.path.join(os.path.dirname(__file__), "tests", "fixtures", "sample_project")
10
+
11
+
12
+ class TestDetectLanguage:
13
+ def test_python(self):
14
+ assert detect_language("foo.py") == "python"
15
+
16
+ def test_typescript(self):
17
+ assert detect_language("foo.ts") == "typescript"
18
+
19
+ def test_javascript(self):
20
+ assert detect_language("foo.js") == "javascript"
21
+
22
+ def test_tsx(self):
23
+ assert detect_language("foo.tsx") == "tsx"
24
+
25
+ def test_unknown(self):
26
+ assert detect_language("foo.txt") is None
27
+
28
+ def test_all_extensions_mapped(self):
29
+ for ext, lang in EXTENSION_MAP.items():
30
+ assert lang is not None, f"Extension {ext} mapped to None"
31
+
32
+
33
+ class TestPythonTags:
34
+ def test_extracts_defs_and_refs(self):
35
+ fpath = os.path.join(FIXTURES, "auth.py")
36
+ tags = get_tags(fpath, "auth.py")
37
+ defs = [t for t in tags if t.kind == "def"]
38
+ refs = [t for t in tags if t.kind == "ref"]
39
+ assert len(defs) >= 3, f"Expected >=3 defs, got {len(defs)}: {[t.name for t in defs]}"
40
+ assert len(refs) >= 2, f"Expected >=2 refs, got {len(refs)}: {[t.name for t in refs]}"
41
+
42
+ def test_def_names_correct(self):
43
+ fpath = os.path.join(FIXTURES, "auth.py")
44
+ tags = get_tags(fpath, "auth.py")
45
+ def_names = {t.name for t in tags if t.kind == "def"}
46
+ assert "handleAuth" in def_names
47
+ assert "validateToken" in def_names
48
+ assert "getUser" in def_names
49
+
50
+ def test_ref_names_correct(self):
51
+ fpath = os.path.join(FIXTURES, "auth.py")
52
+ tags = get_tags(fpath, "auth.py")
53
+ ref_names = {t.name for t in tags if t.kind == "ref"}
54
+ assert "validateToken" in ref_names
55
+ assert "getUser" in ref_names
56
+
57
+ def test_no_garbled_names(self):
58
+ fpath = os.path.join(FIXTURES, "auth.py")
59
+ tags = get_tags(fpath, "auth.py")
60
+ for tag in tags:
61
+ assert tag.name.isidentifier(), f"Garbled name: {tag.name}"
62
+
63
+ def test_tag_has_correct_fields(self):
64
+ fpath = os.path.join(FIXTURES, "auth.py")
65
+ tags = get_tags(fpath, "auth.py")
66
+ assert len(tags) > 0
67
+ tag = tags[0]
68
+ assert hasattr(tag, "rel_fname")
69
+ assert hasattr(tag, "fname")
70
+ assert hasattr(tag, "line")
71
+ assert hasattr(tag, "name")
72
+ assert hasattr(tag, "kind")
73
+ assert tag.kind in ("def", "ref")
74
+
75
+ def test_rel_fname_propagated(self):
76
+ fpath = os.path.join(FIXTURES, "auth.py")
77
+ tags = get_tags(fpath, "auth.py")
78
+ for tag in tags:
79
+ assert tag.rel_fname == "auth.py"
80
+
81
+ def test_no_duplicates(self):
82
+ fpath = os.path.join(FIXTURES, "auth.py")
83
+ tags = get_tags(fpath, "auth.py")
84
+ seen = set()
85
+ for tag in tags:
86
+ key = (tag.name, tag.line, tag.kind)
87
+ assert key not in seen, f"Duplicate tag: {tag}"
88
+ seen.add(key)
89
+
90
+
91
+ class TestTypeScriptTags:
92
+ def test_extracts_defs_and_refs(self):
93
+ fpath = os.path.join(FIXTURES, "api.ts")
94
+ tags = get_tags(fpath, "api.ts")
95
+ defs = [t for t in tags if t.kind == "def"]
96
+ refs = [t for t in tags if t.kind == "ref"]
97
+ assert len(defs) >= 3, f"Expected >=3 defs, got {len(defs)}: {[t.name for t in defs]}"
98
+ assert len(refs) >= 2, f"Expected >=2 refs, got {len(refs)}: {[t.name for t in refs]}"
99
+
100
+ def test_captures_class(self):
101
+ fpath = os.path.join(FIXTURES, "api.ts")
102
+ tags = get_tags(fpath, "api.ts")
103
+ def_names = {t.name for t in tags if t.kind == "def"}
104
+ assert "ApiController" in def_names
105
+
106
+ def test_captures_function_defs(self):
107
+ fpath = os.path.join(FIXTURES, "api.ts")
108
+ tags = get_tags(fpath, "api.ts")
109
+ def_names = {t.name for t in tags if t.kind == "def"}
110
+ assert "processRequest" in def_names
111
+ assert "formatResponse" in def_names
112
+
113
+ def test_captures_call_refs(self):
114
+ fpath = os.path.join(FIXTURES, "api.ts")
115
+ tags = get_tags(fpath, "api.ts")
116
+ ref_names = {t.name for t in tags if t.kind == "ref"}
117
+ assert "handleAuth" in ref_names or "formatResponse" in ref_names
118
+
119
+ def test_captures_cross_file_ref(self):
120
+ """api.ts imports and calls handleAuth from auth.py -- should appear as ref."""
121
+ fpath = os.path.join(FIXTURES, "api.ts")
122
+ tags = get_tags(fpath, "api.ts")
123
+ ref_names = {t.name for t in tags if t.kind == "ref"}
124
+ assert "handleAuth" in ref_names
125
+
126
+
127
+ class TestJavaScriptTags:
128
+ def test_extracts_defs_and_refs(self):
129
+ fpath = os.path.join(FIXTURES, "utils.js")
130
+ tags = get_tags(fpath, "utils.js")
131
+ defs = [t for t in tags if t.kind == "def"]
132
+ refs = [t for t in tags if t.kind == "ref"]
133
+ assert len(defs) >= 3, f"Expected >=3 defs, got {len(defs)}: {[t.name for t in defs]}"
134
+ assert len(refs) >= 2, f"Expected >=2 refs, got {len(refs)}: {[t.name for t in refs]}"
135
+
136
+ def test_arrow_function_defs(self):
137
+ fpath = os.path.join(FIXTURES, "utils.js")
138
+ tags = get_tags(fpath, "utils.js")
139
+ def_names = {t.name for t in tags if t.kind == "def"}
140
+ assert "processData" in def_names
141
+
142
+ def test_regular_function_defs(self):
143
+ fpath = os.path.join(FIXTURES, "utils.js")
144
+ tags = get_tags(fpath, "utils.js")
145
+ def_names = {t.name for t in tags if t.kind == "def"}
146
+ assert "formatDate" in def_names
147
+ assert "parseConfig" in def_names
148
+
149
+ def test_cross_function_refs(self):
150
+ fpath = os.path.join(FIXTURES, "utils.js")
151
+ tags = get_tags(fpath, "utils.js")
152
+ ref_names = {t.name for t in tags if t.kind == "ref"}
153
+ assert "parseConfig" in ref_names
154
+ assert "formatDate" in ref_names
155
+
156
+
157
+ class TestNonexistentFile:
158
+ def test_returns_empty(self):
159
+ tags = get_tags("/nonexistent/file.py", "file.py")
160
+ assert tags == []
161
+
162
+ def test_unknown_extension(self):
163
+ tags = get_tags("file.xyz", "file.xyz")
164
+ assert tags == []
165
+
166
+
167
+ class TestTagNamedTuple:
168
+ def test_tag_is_namedtuple(self):
169
+ tag = Tag(rel_fname="a.py", fname="/a.py", line=1, name="foo", kind="def")
170
+ assert tag.rel_fname == "a.py"
171
+ assert tag.fname == "/a.py"
172
+ assert tag.line == 1
173
+ assert tag.name == "foo"
174
+ assert tag.kind == "def"
@@ -1,66 +1,183 @@
1
- """Tests for ftm-map query module."""
1
+ """Tests for query.py -- all query modes with new schema."""
2
2
  import os
3
3
  import sys
4
4
  import tempfile
5
- import unittest
5
+ import pytest
6
6
 
7
7
  sys.path.insert(0, os.path.dirname(__file__))
8
- from db import get_connection, add_symbol, add_edge
9
- from query import blast_radius, dependency_chain, search, symbol_info
10
-
11
- class TestQuery(unittest.TestCase):
12
- def setUp(self):
13
- self.tmpdir = tempfile.mkdtemp()
14
- self.conn = get_connection(self.tmpdir)
15
- # Build test graph: A -> B -> C, D imports A
16
- self.a = add_symbol(self.conn, "handleAuth", "function", "auth.py", 1, 5, "def handleAuth(req)", "Auth handler")
17
- self.b = add_symbol(self.conn, "validateToken", "function", "auth.py", 7, 10, "def validateToken(token)")
18
- self.c = add_symbol(self.conn, "getUser", "function", "users.py", 1, 5, "def getUser(uid)")
19
- self.d = add_symbol(self.conn, "processRequest", "function", "api.ts", 1, 8, "function processRequest(req)")
20
- add_edge(self.conn, self.a, self.b, "calls")
21
- add_edge(self.conn, self.a, self.c, "calls")
22
- add_edge(self.conn, self.d, self.a, "calls")
23
- self.conn.commit()
24
-
25
- def tearDown(self):
26
- self.conn.close()
27
-
28
- def test_blast_radius(self):
29
- """Blast radius of getUser should include handleAuth and processRequest."""
30
- result = blast_radius(self.conn, "getUser")
31
- names = {r["name"] for r in result["results"]}
32
- self.assertIn("handleAuth", names)
33
- self.assertIn("processRequest", names)
34
- self.assertEqual(result["affected_count"], 2)
35
-
36
- def test_dependency_chain(self):
37
- """handleAuth depends on validateToken and getUser."""
38
- result = dependency_chain(self.conn, "handleAuth")
39
- names = {r["name"] for r in result["results"]}
40
- self.assertIn("validateToken", names)
41
- self.assertIn("getUser", names)
42
-
43
- def test_search(self):
44
- """Search for 'auth' should return handleAuth."""
45
- result = search(self.conn, "auth")
46
- self.assertGreater(result["result_count"], 0)
47
- names = {r["name"] for r in result["results"]}
48
- self.assertIn("handleAuth", names)
49
-
50
- def test_symbol_info(self):
51
- """Should return full details with callers and callees."""
52
- result = symbol_info(self.conn, "handleAuth")
53
- self.assertEqual(result["name"], "handleAuth")
54
- callee_names = {c["name"] for c in result["callees"]}
55
- self.assertIn("validateToken", callee_names)
56
- self.assertIn("getUser", callee_names)
57
- caller_names = {c["name"] for c in result["callers"]}
58
- self.assertIn("processRequest", caller_names)
59
-
60
- def test_missing_symbol(self):
61
- """Query for non-existent symbol should return error."""
62
- result = blast_radius(self.conn, "nonexistent")
63
- self.assertIn("error", result)
64
-
65
- if __name__ == "__main__":
66
- unittest.main()
8
+ from db import get_connection, add_file, add_symbol, add_reference, rebuild_file_edges, rebuild_symbol_edges
9
+ from query import blast_radius, dependency_chain, search, symbol_info, context, stats
10
+
11
+
12
+ @pytest.fixture
13
+ def indexed_conn():
14
+ """Connection with a fully indexed mini-project."""
15
+ with tempfile.TemporaryDirectory() as tmp:
16
+ conn = get_connection(tmp)
17
+
18
+ f1 = add_file(conn, "src/auth.py", "python", 1.0, line_count=50)
19
+ f2 = add_file(conn, "src/api.py", "python", 1.0, line_count=100)
20
+ f3 = add_file(conn, "src/utils.py", "python", 1.0, line_count=30)
21
+
22
+ add_symbol(conn, f1, "authenticate", "function", 1, 20, signature="def authenticate(req)")
23
+ add_symbol(conn, f1, "verify_token", "function", 25, 40)
24
+ add_symbol(conn, f2, "handle_request", "function", 1, 50)
25
+ add_symbol(conn, f3, "format_date", "function", 1, 10)
26
+ add_symbol(conn, f3, "parse_config", "function", 15, 25)
27
+
28
+ add_reference(conn, f2, "authenticate", 10)
29
+ add_reference(conn, f2, "format_date", 20)
30
+ add_reference(conn, f1, "format_date", 30)
31
+
32
+ rebuild_file_edges(conn)
33
+ rebuild_symbol_edges(conn)
34
+ conn.commit()
35
+
36
+ yield conn
37
+ conn.close()
38
+
39
+
40
+ class TestBlastRadius:
41
+ def test_finds_affected(self, indexed_conn):
42
+ result = blast_radius(indexed_conn, "authenticate")
43
+ assert result["affected_count"] >= 1
44
+
45
+ def test_returns_symbol_name(self, indexed_conn):
46
+ result = blast_radius(indexed_conn, "authenticate")
47
+ assert result["symbol"] == "authenticate"
48
+
49
+ def test_results_have_file_path(self, indexed_conn):
50
+ result = blast_radius(indexed_conn, "authenticate")
51
+ if result["results"]:
52
+ assert "file_path" in result["results"][0]
53
+
54
+ def test_results_have_depth(self, indexed_conn):
55
+ result = blast_radius(indexed_conn, "authenticate")
56
+ if result["results"]:
57
+ assert "depth" in result["results"][0]
58
+
59
+ def test_not_found(self, indexed_conn):
60
+ result = blast_radius(indexed_conn, "nonexistent")
61
+ assert "error" in result
62
+
63
+
64
+ class TestDependencyChain:
65
+ def test_finds_deps(self, indexed_conn):
66
+ result = dependency_chain(indexed_conn, "handle_request")
67
+ assert result["dependency_count"] >= 1
68
+
69
+ def test_returns_symbol_name(self, indexed_conn):
70
+ result = dependency_chain(indexed_conn, "handle_request")
71
+ assert result["symbol"] == "handle_request"
72
+
73
+ def test_not_found(self, indexed_conn):
74
+ result = dependency_chain(indexed_conn, "nonexistent")
75
+ assert "error" in result
76
+
77
+ def test_leaf_has_no_deps(self, indexed_conn):
78
+ result = dependency_chain(indexed_conn, "verify_token")
79
+ # verify_token has no references to other symbols in its scope
80
+ assert result["dependency_count"] == 0 or "error" not in result
81
+
82
+
83
+ class TestSearch:
84
+ def test_finds_by_name(self, indexed_conn):
85
+ result = search(indexed_conn, "authenticate")
86
+ assert result["result_count"] >= 1
87
+
88
+ def test_returns_query(self, indexed_conn):
89
+ result = search(indexed_conn, "authenticate")
90
+ assert result["query"] == "authenticate"
91
+
92
+ def test_results_have_file_path(self, indexed_conn):
93
+ result = search(indexed_conn, "authenticate")
94
+ if result["results"]:
95
+ assert "file_path" in result["results"][0]
96
+
97
+ def test_results_have_rank(self, indexed_conn):
98
+ result = search(indexed_conn, "authenticate")
99
+ if result["results"]:
100
+ assert "rank" in result["results"][0]
101
+
102
+ def test_empty_results(self, indexed_conn):
103
+ result = search(indexed_conn, "zzzznonexistent")
104
+ assert result["result_count"] == 0
105
+
106
+
107
+ class TestSymbolInfo:
108
+ def test_returns_name(self, indexed_conn):
109
+ result = symbol_info(indexed_conn, "authenticate")
110
+ assert result["name"] == "authenticate"
111
+
112
+ def test_returns_kind(self, indexed_conn):
113
+ result = symbol_info(indexed_conn, "authenticate")
114
+ assert "kind" in result
115
+
116
+ def test_returns_reference_count(self, indexed_conn):
117
+ result = symbol_info(indexed_conn, "authenticate")
118
+ assert "reference_count" in result
119
+ assert result["reference_count"] >= 1 # api.py references it
120
+
121
+ def test_returns_callers_callees(self, indexed_conn):
122
+ result = symbol_info(indexed_conn, "authenticate")
123
+ assert "callers" in result
124
+ assert "callees" in result
125
+
126
+ def test_returns_blast_radius_count(self, indexed_conn):
127
+ result = symbol_info(indexed_conn, "authenticate")
128
+ assert "blast_radius_count" in result
129
+
130
+ def test_returns_file(self, indexed_conn):
131
+ result = symbol_info(indexed_conn, "authenticate")
132
+ assert result["file"] == "src/auth.py"
133
+
134
+ def test_not_found(self, indexed_conn):
135
+ result = symbol_info(indexed_conn, "nonexistent")
136
+ assert "error" in result
137
+
138
+
139
+ class TestContext:
140
+ def test_returns_files(self, indexed_conn):
141
+ result = context(indexed_conn, seed_symbols=["authenticate"])
142
+ assert "files" in result
143
+ assert len(result["files"]) > 0
144
+
145
+ def test_files_have_path(self, indexed_conn):
146
+ result = context(indexed_conn, seed_symbols=["authenticate"])
147
+ if result["files"]:
148
+ assert "path" in result["files"][0]
149
+
150
+ def test_files_have_score(self, indexed_conn):
151
+ result = context(indexed_conn, seed_symbols=["authenticate"])
152
+ if result["files"]:
153
+ assert "score" in result["files"][0]
154
+
155
+ def test_respects_budget(self, indexed_conn):
156
+ result = context(indexed_conn, token_budget=50)
157
+ if result.get("total_tokens"):
158
+ assert result["total_tokens"] <= 50 * 1.15
159
+
160
+ def test_no_budget_returns_all(self, indexed_conn):
161
+ result = context(indexed_conn, token_budget=None)
162
+ assert "files" in result
163
+
164
+ def test_seed_files(self, indexed_conn):
165
+ result = context(indexed_conn, seed_files=["src/auth.py"])
166
+ assert "files" in result
167
+ assert len(result["files"]) > 0
168
+
169
+
170
+ class TestStats:
171
+ def test_returns_counts(self, indexed_conn):
172
+ result = stats(indexed_conn)
173
+ assert result["file_count"] == 3
174
+ assert result["symbol_count"] == 5
175
+
176
+ def test_returns_edge_counts(self, indexed_conn):
177
+ result = stats(indexed_conn)
178
+ assert "edge_count" in result
179
+ assert "file_edge_count" in result
180
+
181
+ def test_returns_reference_count(self, indexed_conn):
182
+ result = stats(indexed_conn)
183
+ assert result["reference_count"] == 3