feed-the-machine 1.3.1 → 1.4.1
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.
- package/README.md +82 -180
- package/ftm-git/SKILL.md +0 -1
- package/ftm-map/SKILL.md +46 -14
- package/ftm-map/scripts/db.py +439 -118
- package/ftm-map/scripts/index.py +128 -54
- package/ftm-map/scripts/parser.py +89 -320
- package/ftm-map/scripts/queries/go-tags.scm +20 -0
- package/ftm-map/scripts/queries/javascript-tags.scm +19 -7
- package/ftm-map/scripts/queries/python-tags.scm +22 -8
- package/ftm-map/scripts/queries/ruby-tags.scm +19 -0
- package/ftm-map/scripts/queries/rust-tags.scm +37 -0
- package/ftm-map/scripts/queries/typescript-tags.scm +20 -8
- package/ftm-map/scripts/query.py +176 -24
- package/ftm-map/scripts/ranker.py +377 -0
- package/ftm-map/scripts/requirements.txt +3 -0
- package/ftm-map/scripts/setup.sh +11 -0
- package/ftm-map/scripts/test_db.py +355 -115
- package/ftm-map/scripts/test_parser.py +169 -101
- package/ftm-map/scripts/test_query.py +178 -61
- package/ftm-map/scripts/test_ranker.py +199 -0
- package/ftm-map/scripts/views.py +107 -61
- package/ftm-mind/references/event-registry.md +0 -10
- package/hooks/ftm-blackboard-enforcer.sh +1 -4
- package/package.json +1 -1
- package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/__pycache__/generator.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/__pycache__/schema.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
- package/ftm-map/scripts/tests/fixtures/__init__.py +0 -0
- package/ftm-map/scripts/tests/fixtures/sample_project/api.ts +0 -16
- package/ftm-map/scripts/tests/fixtures/sample_project/auth.py +0 -15
- package/ftm-map/scripts/tests/fixtures/sample_project/utils.js +0 -16
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
; Functions
|
|
2
2
|
(function_declaration
|
|
3
|
-
name: (identifier) @name) @definition.function
|
|
3
|
+
name: (identifier) @name.definition.function) @definition.function
|
|
4
4
|
|
|
5
5
|
; Methods
|
|
6
6
|
(method_definition
|
|
7
|
-
name: (property_identifier) @name) @definition.method
|
|
7
|
+
name: (property_identifier) @name.definition.method) @definition.method
|
|
8
8
|
|
|
9
9
|
; Classes
|
|
10
10
|
(class_declaration
|
|
11
|
-
name: (type_identifier) @name) @definition.class
|
|
11
|
+
name: (type_identifier) @name.definition.class) @definition.class
|
|
12
12
|
|
|
13
|
-
; Arrow functions assigned to const/let
|
|
13
|
+
; Arrow functions assigned to const/let
|
|
14
14
|
(lexical_declaration
|
|
15
15
|
(variable_declarator
|
|
16
|
-
name: (identifier) @name
|
|
16
|
+
name: (identifier) @name.definition.function
|
|
17
17
|
value: (arrow_function))) @definition.function
|
|
18
18
|
|
|
19
19
|
; Interfaces
|
|
20
20
|
(interface_declaration
|
|
21
|
-
name: (type_identifier) @name) @definition.
|
|
21
|
+
name: (type_identifier) @name.definition.interface) @definition.interface
|
|
22
22
|
|
|
23
23
|
; Type aliases
|
|
24
24
|
(type_alias_declaration
|
|
25
|
-
name: (type_identifier) @name) @definition.type
|
|
25
|
+
name: (type_identifier) @name.definition.type) @definition.type
|
|
26
26
|
|
|
27
27
|
; Enums
|
|
28
28
|
(enum_declaration
|
|
29
|
-
name: (identifier) @name) @definition.
|
|
29
|
+
name: (identifier) @name.definition.enum) @definition.enum
|
|
30
|
+
|
|
31
|
+
; Call references
|
|
32
|
+
(call_expression
|
|
33
|
+
function: [
|
|
34
|
+
(identifier) @name.reference.call
|
|
35
|
+
(member_expression
|
|
36
|
+
property: (property_identifier) @name.reference.call)
|
|
37
|
+
]) @reference.call
|
|
38
|
+
|
|
39
|
+
; New expressions
|
|
40
|
+
(new_expression
|
|
41
|
+
constructor: (identifier) @name.reference.class) @reference.class
|
package/ftm-map/scripts/query.py
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""ftm-map query interface: structural and text queries against the code graph.
|
|
2
|
+
"""ftm-map query interface: structural and text queries against the code graph.
|
|
3
|
+
|
|
4
|
+
Supports five query modes:
|
|
5
|
+
--blast-radius SYMBOL Transitive reverse dependencies (who is affected)
|
|
6
|
+
--deps SYMBOL Transitive forward dependencies (what does it need)
|
|
7
|
+
--search QUERY BM25-ranked full-text search over symbols
|
|
8
|
+
--info SYMBOL Full symbol details with callers, callees, refs
|
|
9
|
+
--context PageRank-based context selection with token budgeting
|
|
10
|
+
--stats Database statistics overview
|
|
11
|
+
|
|
12
|
+
All output is JSON on stdout.
|
|
13
|
+
"""
|
|
3
14
|
|
|
4
15
|
import argparse
|
|
5
16
|
import json
|
|
@@ -8,7 +19,19 @@ import sys
|
|
|
8
19
|
|
|
9
20
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
10
21
|
|
|
11
|
-
from db import
|
|
22
|
+
from db import (
|
|
23
|
+
get_connection,
|
|
24
|
+
get_symbol_by_name,
|
|
25
|
+
get_transitive_deps,
|
|
26
|
+
get_reverse_deps,
|
|
27
|
+
fts_search,
|
|
28
|
+
get_stats,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Query functions
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
12
35
|
|
|
13
36
|
|
|
14
37
|
def blast_radius(conn, symbol_name: str, max_depth: int = 10) -> dict:
|
|
@@ -17,15 +40,34 @@ def blast_radius(conn, symbol_name: str, max_depth: int = 10) -> dict:
|
|
|
17
40
|
if not symbols:
|
|
18
41
|
return {"error": f"Symbol '{symbol_name}' not found", "results": []}
|
|
19
42
|
|
|
20
|
-
# Use first match
|
|
21
43
|
sym = symbols[0]
|
|
44
|
+
# Resolve file path from file_id FK
|
|
45
|
+
file_row = conn.execute(
|
|
46
|
+
"SELECT path FROM files WHERE id=?", (sym["file_id"],)
|
|
47
|
+
).fetchone()
|
|
48
|
+
file_path = file_row["path"] if file_row else "unknown"
|
|
49
|
+
|
|
22
50
|
deps = get_reverse_deps(conn, sym["id"], max_depth)
|
|
23
51
|
|
|
52
|
+
# Enrich each dep with its file path
|
|
53
|
+
enriched = []
|
|
54
|
+
for d in deps:
|
|
55
|
+
dep_file = conn.execute(
|
|
56
|
+
"SELECT path FROM files WHERE id=?", (d["file_id"],)
|
|
57
|
+
).fetchone()
|
|
58
|
+
enriched.append({
|
|
59
|
+
"id": d["id"],
|
|
60
|
+
"name": d["name"],
|
|
61
|
+
"kind": d["kind"],
|
|
62
|
+
"file_path": dep_file["path"] if dep_file else "unknown",
|
|
63
|
+
"depth": d["depth"],
|
|
64
|
+
})
|
|
65
|
+
|
|
24
66
|
return {
|
|
25
67
|
"symbol": symbol_name,
|
|
26
|
-
"symbol_file":
|
|
27
|
-
"affected_count": len(
|
|
28
|
-
"results":
|
|
68
|
+
"symbol_file": file_path,
|
|
69
|
+
"affected_count": len(enriched),
|
|
70
|
+
"results": enriched,
|
|
29
71
|
}
|
|
30
72
|
|
|
31
73
|
|
|
@@ -36,23 +78,58 @@ def dependency_chain(conn, symbol_name: str, max_depth: int = 10) -> dict:
|
|
|
36
78
|
return {"error": f"Symbol '{symbol_name}' not found", "results": []}
|
|
37
79
|
|
|
38
80
|
sym = symbols[0]
|
|
81
|
+
file_row = conn.execute(
|
|
82
|
+
"SELECT path FROM files WHERE id=?", (sym["file_id"],)
|
|
83
|
+
).fetchone()
|
|
84
|
+
file_path = file_row["path"] if file_row else "unknown"
|
|
85
|
+
|
|
39
86
|
deps = get_transitive_deps(conn, sym["id"], max_depth)
|
|
40
87
|
|
|
88
|
+
enriched = []
|
|
89
|
+
for d in deps:
|
|
90
|
+
dep_file = conn.execute(
|
|
91
|
+
"SELECT path FROM files WHERE id=?", (d["file_id"],)
|
|
92
|
+
).fetchone()
|
|
93
|
+
enriched.append({
|
|
94
|
+
"id": d["id"],
|
|
95
|
+
"name": d["name"],
|
|
96
|
+
"kind": d["kind"],
|
|
97
|
+
"file_path": dep_file["path"] if dep_file else "unknown",
|
|
98
|
+
"depth": d["depth"],
|
|
99
|
+
})
|
|
100
|
+
|
|
41
101
|
return {
|
|
42
102
|
"symbol": symbol_name,
|
|
43
|
-
"symbol_file":
|
|
44
|
-
"dependency_count": len(
|
|
45
|
-
"results":
|
|
103
|
+
"symbol_file": file_path,
|
|
104
|
+
"dependency_count": len(enriched),
|
|
105
|
+
"results": enriched,
|
|
46
106
|
}
|
|
47
107
|
|
|
48
108
|
|
|
49
109
|
def search(conn, query_text: str, limit: int = 10) -> dict:
|
|
50
110
|
"""BM25-ranked full-text search."""
|
|
51
111
|
results = fts_search(conn, query_text, limit)
|
|
112
|
+
|
|
113
|
+
# Enrich results with file path from FK
|
|
114
|
+
enriched = []
|
|
115
|
+
for r in results:
|
|
116
|
+
file_row = conn.execute(
|
|
117
|
+
"SELECT path FROM files WHERE id=?", (r["file_id"],)
|
|
118
|
+
).fetchone()
|
|
119
|
+
enriched.append({
|
|
120
|
+
"id": r["id"],
|
|
121
|
+
"name": r["name"],
|
|
122
|
+
"qualified_name": r.get("qualified_name", ""),
|
|
123
|
+
"kind": r["kind"],
|
|
124
|
+
"file_path": file_row["path"] if file_row else "unknown",
|
|
125
|
+
"line_start": r["line_start"],
|
|
126
|
+
"rank": r["rank"],
|
|
127
|
+
})
|
|
128
|
+
|
|
52
129
|
return {
|
|
53
130
|
"query": query_text,
|
|
54
|
-
"result_count": len(
|
|
55
|
-
"results":
|
|
131
|
+
"result_count": len(enriched),
|
|
132
|
+
"results": enriched,
|
|
56
133
|
}
|
|
57
134
|
|
|
58
135
|
|
|
@@ -65,43 +142,93 @@ def symbol_info(conn, symbol_name: str) -> dict:
|
|
|
65
142
|
sym = symbols[0]
|
|
66
143
|
sym_id = sym["id"]
|
|
67
144
|
|
|
68
|
-
#
|
|
145
|
+
# Resolve file path from file_id FK
|
|
146
|
+
file_row = conn.execute(
|
|
147
|
+
"SELECT path FROM files WHERE id=?", (sym["file_id"],)
|
|
148
|
+
).fetchone()
|
|
149
|
+
file_path = file_row["path"] if file_row else "unknown"
|
|
150
|
+
|
|
151
|
+
# Direct callers (who references me) via symbol_edges
|
|
69
152
|
callers = conn.execute(
|
|
70
153
|
"""
|
|
71
|
-
SELECT s.name, s.kind,
|
|
72
|
-
FROM
|
|
73
|
-
|
|
154
|
+
SELECT s.name, s.kind, f.path AS file_path, s.line_start
|
|
155
|
+
FROM symbol_edges se
|
|
156
|
+
JOIN symbols s ON s.id = se.source_symbol_id
|
|
157
|
+
JOIN files f ON f.id = s.file_id
|
|
158
|
+
WHERE se.target_symbol_id = ?
|
|
74
159
|
""",
|
|
75
160
|
(sym_id,),
|
|
76
161
|
).fetchall()
|
|
77
162
|
|
|
78
|
-
# Direct callees (who
|
|
163
|
+
# Direct callees (who I reference) via symbol_edges
|
|
79
164
|
callees = conn.execute(
|
|
80
165
|
"""
|
|
81
|
-
SELECT s.name, s.kind,
|
|
82
|
-
FROM
|
|
83
|
-
|
|
166
|
+
SELECT s.name, s.kind, f.path AS file_path, s.line_start
|
|
167
|
+
FROM symbol_edges se
|
|
168
|
+
JOIN symbols s ON s.id = se.target_symbol_id
|
|
169
|
+
JOIN files f ON f.id = s.file_id
|
|
170
|
+
WHERE se.source_symbol_id = ?
|
|
84
171
|
""",
|
|
85
172
|
(sym_id,),
|
|
86
173
|
).fetchall()
|
|
87
174
|
|
|
175
|
+
# Reference count from refs table
|
|
176
|
+
ref_count = conn.execute(
|
|
177
|
+
"SELECT COUNT(*) FROM refs WHERE symbol_name=?", (sym["name"],)
|
|
178
|
+
).fetchone()[0]
|
|
179
|
+
|
|
88
180
|
# Blast radius count
|
|
89
181
|
blast = get_reverse_deps(conn, sym_id)
|
|
90
182
|
|
|
91
183
|
return {
|
|
92
184
|
"name": sym["name"],
|
|
185
|
+
"qualified_name": sym.get("qualified_name", ""),
|
|
93
186
|
"kind": sym["kind"],
|
|
94
|
-
"file":
|
|
95
|
-
"
|
|
96
|
-
"
|
|
187
|
+
"file": file_path,
|
|
188
|
+
"line_start": sym["line_start"],
|
|
189
|
+
"line_end": sym.get("line_end"),
|
|
97
190
|
"signature": sym.get("signature", ""),
|
|
98
|
-
"doc_comment": sym.get("doc_comment", ""),
|
|
99
191
|
"callers": [dict(r) for r in callers],
|
|
100
192
|
"callees": [dict(r) for r in callees],
|
|
193
|
+
"reference_count": ref_count,
|
|
101
194
|
"blast_radius_count": len(blast),
|
|
102
195
|
}
|
|
103
196
|
|
|
104
197
|
|
|
198
|
+
def context(conn, seed_files=None, seed_keywords=None, seed_symbols=None, token_budget=8000):
|
|
199
|
+
"""PageRank-based context selection with personalization.
|
|
200
|
+
|
|
201
|
+
Uses the ranker module to score files by structural importance,
|
|
202
|
+
optionally biased toward seed files/keywords/symbols. When a token
|
|
203
|
+
budget is provided, fits the highest-ranked files into that budget.
|
|
204
|
+
"""
|
|
205
|
+
from ranker import rank_files, fit_to_budget
|
|
206
|
+
|
|
207
|
+
ranked = rank_files(conn, seed_files, seed_keywords, seed_symbols)
|
|
208
|
+
if not ranked:
|
|
209
|
+
return {"error": "No files in index or no edges to rank", "files": []}
|
|
210
|
+
|
|
211
|
+
if token_budget:
|
|
212
|
+
files, total_tokens = fit_to_budget(ranked, conn, token_budget)
|
|
213
|
+
return {"files": files, "total_tokens": total_tokens}
|
|
214
|
+
else:
|
|
215
|
+
# Return all files with scores, no budget constraint
|
|
216
|
+
return {
|
|
217
|
+
"files": [{"path": p, "score": round(s, 6)} for p, s in ranked],
|
|
218
|
+
"total_tokens": None,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def stats(conn):
|
|
223
|
+
"""Show database statistics."""
|
|
224
|
+
return get_stats(conn)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
# CLI
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
|
|
105
232
|
def main():
|
|
106
233
|
parser = argparse.ArgumentParser(description="ftm-map query interface")
|
|
107
234
|
parser.add_argument(
|
|
@@ -112,6 +239,24 @@ def main():
|
|
|
112
239
|
)
|
|
113
240
|
parser.add_argument("--search", metavar="QUERY", help="Full-text search")
|
|
114
241
|
parser.add_argument("--info", metavar="SYMBOL", help="Full symbol info")
|
|
242
|
+
parser.add_argument(
|
|
243
|
+
"--context", action="store_true", help="PageRank context selection"
|
|
244
|
+
)
|
|
245
|
+
parser.add_argument(
|
|
246
|
+
"--seed-files", nargs="*", help="Seed files for context personalization"
|
|
247
|
+
)
|
|
248
|
+
parser.add_argument(
|
|
249
|
+
"--seed-keywords", nargs="*", help="Seed keywords for context personalization"
|
|
250
|
+
)
|
|
251
|
+
parser.add_argument(
|
|
252
|
+
"--seed-symbols", nargs="*", help="Seed symbols for context personalization"
|
|
253
|
+
)
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"--token-budget", type=int, default=8000, help="Token budget for context output"
|
|
256
|
+
)
|
|
257
|
+
parser.add_argument(
|
|
258
|
+
"--stats", action="store_true", help="Show database statistics"
|
|
259
|
+
)
|
|
115
260
|
parser.add_argument(
|
|
116
261
|
"--limit", type=int, default=10, help="Result limit for search"
|
|
117
262
|
)
|
|
@@ -128,7 +273,14 @@ def main():
|
|
|
128
273
|
|
|
129
274
|
conn = get_connection(args.project_root)
|
|
130
275
|
try:
|
|
131
|
-
if args.
|
|
276
|
+
if args.context:
|
|
277
|
+
result = context(
|
|
278
|
+
conn, args.seed_files, args.seed_keywords,
|
|
279
|
+
args.seed_symbols, args.token_budget,
|
|
280
|
+
)
|
|
281
|
+
elif args.stats:
|
|
282
|
+
result = stats(conn)
|
|
283
|
+
elif args.blast_radius:
|
|
132
284
|
result = blast_radius(conn, args.blast_radius, args.max_depth)
|
|
133
285
|
elif args.deps:
|
|
134
286
|
result = dependency_chain(conn, args.deps, args.max_depth)
|