feed-the-machine 1.6.1 → 1.7.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.
- package/LICENSE +21 -21
- package/README.md +170 -170
- package/bin/brain.py +1340 -0
- package/bin/convert_claude_skills_to_codex.py +490 -0
- package/bin/generate-manifest.mjs +463 -463
- package/bin/harden_codex_skills.py +141 -0
- package/bin/install.mjs +491 -491
- package/bin/migrate-eng-buddy-data.py +875 -0
- package/bin/playbook_engine/__init__.py +1 -0
- package/bin/playbook_engine/conftest.py +8 -0
- package/bin/playbook_engine/extractor.py +33 -0
- package/bin/playbook_engine/manager.py +102 -0
- package/bin/playbook_engine/models.py +84 -0
- package/bin/playbook_engine/registry.py +35 -0
- package/bin/playbook_engine/test_extractor.py +72 -0
- package/bin/playbook_engine/test_integration.py +129 -0
- package/bin/playbook_engine/test_manager.py +85 -0
- package/bin/playbook_engine/test_models.py +166 -0
- package/bin/playbook_engine/test_registry.py +67 -0
- package/bin/playbook_engine/test_tracer.py +86 -0
- package/bin/playbook_engine/tracer.py +93 -0
- package/bin/tasks_db.py +456 -0
- package/docs/HOOKS.md +243 -243
- package/docs/INBOX.md +233 -233
- package/ftm/SKILL.md +125 -122
- package/ftm-audit/SKILL.md +623 -623
- package/ftm-audit/references/protocols/PROJECT-PATTERNS.md +91 -91
- package/ftm-audit/references/protocols/RUNTIME-WIRING.md +66 -66
- package/ftm-audit/references/protocols/WIRING-CONTRACTS.md +135 -135
- package/ftm-audit/references/strategies/AUTO-FIX-STRATEGIES.md +69 -69
- package/ftm-audit/references/templates/REPORT-FORMAT.md +96 -96
- package/ftm-audit/scripts/run-knip.sh +23 -23
- package/ftm-audit.yml +2 -2
- package/ftm-brainstorm/SKILL.md +1003 -498
- package/ftm-brainstorm/evals/evals.json +180 -100
- package/ftm-brainstorm/evals/promptfoo.yaml +109 -109
- package/ftm-brainstorm/references/agent-prompts.md +552 -224
- package/ftm-brainstorm/references/plan-template.md +209 -121
- package/ftm-brainstorm.yml +2 -2
- package/ftm-browse/SKILL.md +454 -454
- package/ftm-browse/daemon/browser-manager.ts +206 -206
- package/ftm-browse/daemon/bun.lock +30 -30
- package/ftm-browse/daemon/cli.ts +347 -347
- package/ftm-browse/daemon/commands.ts +410 -410
- package/ftm-browse/daemon/main.ts +357 -357
- package/ftm-browse/daemon/package.json +17 -17
- package/ftm-browse/daemon/server.ts +189 -189
- package/ftm-browse/daemon/snapshot.ts +519 -519
- package/ftm-browse/daemon/tsconfig.json +22 -22
- package/ftm-browse.yml +4 -4
- package/ftm-capture/SKILL.md +370 -370
- package/ftm-capture.yml +4 -4
- package/ftm-codex-gate/SKILL.md +361 -361
- package/ftm-codex-gate.yml +2 -2
- package/ftm-config/SKILL.md +422 -345
- package/ftm-config.default.yml +125 -82
- package/ftm-config.yml +44 -2
- package/ftm-council/SKILL.md +416 -416
- package/ftm-council/references/prompts/CLAUDE-INVESTIGATION.md +60 -60
- package/ftm-council/references/prompts/CODEX-INVESTIGATION.md +58 -58
- package/ftm-council/references/prompts/GEMINI-INVESTIGATION.md +58 -58
- package/ftm-council/references/prompts/REBUTTAL-TEMPLATE.md +57 -57
- package/ftm-council/references/protocols/PREREQUISITES.md +47 -47
- package/ftm-council/references/protocols/STEP-0-FRAMING.md +46 -46
- package/ftm-council.yml +2 -2
- package/ftm-dashboard/SKILL.md +163 -163
- package/ftm-dashboard.yml +4 -4
- package/ftm-debug/SKILL.md +1037 -1037
- package/ftm-debug/references/phases/PHASE-0-INTAKE.md +58 -58
- package/ftm-debug/references/phases/PHASE-1-TRIAGE.md +46 -46
- package/ftm-debug/references/phases/PHASE-2-WAR-ROOM-AGENTS.md +279 -279
- package/ftm-debug/references/phases/PHASE-3-TO-6-EXECUTION.md +436 -436
- package/ftm-debug/references/protocols/BLACKBOARD.md +86 -86
- package/ftm-debug/references/protocols/EDGE-CASES.md +103 -103
- package/ftm-debug.yml +2 -2
- package/ftm-diagram/SKILL.md +277 -277
- package/ftm-diagram.yml +2 -2
- package/ftm-executor/SKILL.md +777 -777
- package/ftm-executor/references/STYLE-TEMPLATE.md +73 -73
- package/ftm-executor/references/phases/PHASE-0-VERIFICATION.md +62 -62
- package/ftm-executor/references/phases/PHASE-2-AGENT-ASSEMBLY.md +34 -34
- package/ftm-executor/references/phases/PHASE-3-WORKTREES.md +38 -38
- package/ftm-executor/references/phases/PHASE-4-5-AUDIT.md +72 -72
- package/ftm-executor/references/phases/PHASE-4-DISPATCH.md +66 -66
- package/ftm-executor/references/phases/PHASE-5-5-CODEX-GATE.md +73 -73
- package/ftm-executor/references/protocols/DOCUMENTATION-BOOTSTRAP.md +36 -36
- package/ftm-executor/references/protocols/MODEL-PROFILE.md +59 -59
- package/ftm-executor/references/protocols/PROGRESS-TRACKING.md +66 -66
- package/ftm-executor/runtime/ftm-runtime.mjs +252 -252
- package/ftm-executor/runtime/package.json +8 -8
- package/ftm-executor.yml +2 -2
- package/ftm-git/SKILL.md +441 -441
- package/ftm-git/evals/evals.json +26 -26
- package/ftm-git/evals/promptfoo.yaml +75 -75
- package/ftm-git/hooks/post-commit-experience.sh +92 -92
- package/ftm-git/references/patterns/SECRET-PATTERNS.md +104 -104
- package/ftm-git/references/protocols/REMEDIATION.md +139 -139
- package/ftm-git/scripts/pre-commit-secrets.sh +110 -110
- package/ftm-git.yml +2 -2
- package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/adapters/_retry.py +64 -64
- package/ftm-inbox/backend/adapters/base.py +230 -230
- package/ftm-inbox/backend/adapters/freshservice.py +104 -104
- package/ftm-inbox/backend/adapters/gmail.py +125 -125
- package/ftm-inbox/backend/adapters/jira.py +136 -136
- package/ftm-inbox/backend/adapters/registry.py +192 -192
- package/ftm-inbox/backend/adapters/slack.py +110 -110
- package/ftm-inbox/backend/db/connection.py +54 -54
- package/ftm-inbox/backend/db/schema.py +78 -78
- package/ftm-inbox/backend/executor/__init__.py +7 -7
- package/ftm-inbox/backend/executor/engine.py +149 -149
- package/ftm-inbox/backend/executor/step_runner.py +98 -98
- package/ftm-inbox/backend/main.py +103 -103
- package/ftm-inbox/backend/models/__init__.py +1 -1
- package/ftm-inbox/backend/models/unified_task.py +36 -36
- package/ftm-inbox/backend/planner/__init__.py +6 -6
- 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/planner/generator.py +127 -127
- package/ftm-inbox/backend/planner/schema.py +34 -34
- package/ftm-inbox/backend/requirements.txt +5 -5
- package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/routes/execute.py +186 -186
- package/ftm-inbox/backend/routes/health.py +52 -52
- package/ftm-inbox/backend/routes/inbox.py +68 -68
- package/ftm-inbox/backend/routes/plan.py +271 -271
- package/ftm-inbox/bin/launchagent.mjs +91 -91
- package/ftm-inbox/bin/setup.mjs +188 -188
- package/ftm-inbox/bin/start.sh +10 -10
- package/ftm-inbox/bin/status.sh +17 -17
- package/ftm-inbox/bin/stop.sh +8 -8
- package/ftm-inbox/config.example.yml +55 -55
- package/ftm-inbox/package-lock.json +2898 -2898
- package/ftm-inbox/package.json +26 -26
- package/ftm-inbox/postcss.config.js +6 -6
- package/ftm-inbox/src/app.css +199 -199
- package/ftm-inbox/src/app.html +18 -18
- package/ftm-inbox/src/lib/api.ts +166 -166
- package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -81
- package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -143
- package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -271
- package/ftm-inbox/src/lib/components/PlanView.svelte +206 -206
- package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -99
- package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -190
- package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -63
- package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -86
- package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -106
- package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -67
- package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -149
- package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -80
- package/ftm-inbox/src/lib/theme.ts +47 -47
- package/ftm-inbox/src/routes/+layout.svelte +76 -76
- package/ftm-inbox/src/routes/+page.svelte +401 -401
- package/ftm-inbox/svelte.config.js +12 -12
- package/ftm-inbox/tailwind.config.ts +63 -63
- package/ftm-inbox/tsconfig.json +13 -13
- package/ftm-inbox/vite.config.ts +6 -6
- package/ftm-intent/SKILL.md +241 -241
- package/ftm-intent.yml +2 -2
- package/ftm-manifest.json +3794 -3794
- package/ftm-map/SKILL.md +291 -291
- package/ftm-map/scripts/db.py +712 -712
- package/ftm-map/scripts/index.py +415 -415
- package/ftm-map/scripts/parser.py +224 -224
- package/ftm-map/scripts/queries/go-tags.scm +20 -20
- package/ftm-map/scripts/queries/javascript-tags.scm +35 -35
- package/ftm-map/scripts/queries/python-tags.scm +31 -31
- package/ftm-map/scripts/queries/ruby-tags.scm +19 -19
- package/ftm-map/scripts/queries/rust-tags.scm +37 -37
- package/ftm-map/scripts/queries/typescript-tags.scm +41 -41
- package/ftm-map/scripts/query.py +301 -301
- package/ftm-map/scripts/ranker.py +377 -377
- package/ftm-map/scripts/requirements.txt +5 -5
- package/ftm-map/scripts/setup-hooks.sh +27 -27
- package/ftm-map/scripts/setup.sh +56 -56
- package/ftm-map/scripts/test_db.py +364 -364
- package/ftm-map/scripts/test_parser.py +174 -174
- package/ftm-map/scripts/test_query.py +183 -183
- package/ftm-map/scripts/test_ranker.py +199 -199
- package/ftm-map/scripts/views.py +591 -591
- package/ftm-map.yml +2 -2
- package/ftm-mind/SKILL.md +201 -1943
- package/ftm-mind/evals/promptfoo.yaml +142 -142
- package/ftm-mind/references/blackboard-protocol.md +110 -0
- package/ftm-mind/references/blackboard-schema.md +328 -328
- package/ftm-mind/references/complexity-guide.md +110 -110
- package/ftm-mind/references/complexity-sizing.md +138 -0
- package/ftm-mind/references/decide-act-protocol.md +172 -0
- package/ftm-mind/references/direct-execution.md +51 -0
- package/ftm-mind/references/environment-discovery.md +77 -0
- package/ftm-mind/references/event-registry.md +319 -319
- package/ftm-mind/references/mcp-inventory.md +300 -296
- package/ftm-mind/references/ops-routing.md +47 -0
- package/ftm-mind/references/orient-protocol.md +234 -0
- package/ftm-mind/references/personality.md +40 -0
- package/ftm-mind/references/protocols/COMPLEXITY-SIZING.md +72 -72
- package/ftm-mind/references/protocols/MCP-HEURISTICS.md +32 -32
- package/ftm-mind/references/protocols/PLAN-APPROVAL.md +80 -80
- package/ftm-mind/references/reflexion-protocol.md +249 -249
- package/ftm-mind/references/routing/SCENARIOS.md +22 -22
- package/ftm-mind/references/routing-scenarios.md +35 -35
- package/ftm-mind.yml +2 -2
- package/ftm-ops.yml +4 -0
- package/ftm-pause/SKILL.md +395 -395
- package/ftm-pause/references/protocols/SKILL-RESTORE-PROTOCOLS.md +186 -186
- package/ftm-pause/references/protocols/VALIDATION.md +80 -80
- package/ftm-pause.yml +2 -2
- package/ftm-researcher/SKILL.md +275 -275
- package/ftm-researcher/evals/agent-diversity.yaml +17 -17
- package/ftm-researcher/evals/synthesis-quality.yaml +12 -12
- package/ftm-researcher/evals/trigger-accuracy.yaml +39 -39
- package/ftm-researcher/references/adaptive-search.md +116 -116
- package/ftm-researcher/references/agent-prompts.md +193 -193
- package/ftm-researcher/references/council-integration.md +193 -193
- package/ftm-researcher/references/output-format.md +203 -203
- package/ftm-researcher/references/synthesis-pipeline.md +165 -165
- package/ftm-researcher/scripts/score_credibility.py +234 -234
- package/ftm-researcher/scripts/validate_research.py +92 -92
- package/ftm-researcher.yml +2 -2
- package/ftm-resume/SKILL.md +518 -518
- package/ftm-resume/references/protocols/VALIDATION.md +172 -172
- package/ftm-resume.yml +2 -2
- package/ftm-retro/SKILL.md +380 -380
- package/ftm-retro/references/protocols/SCORING-RUBRICS.md +89 -89
- package/ftm-retro/references/templates/REPORT-FORMAT.md +109 -109
- package/ftm-retro.yml +2 -2
- package/ftm-routine/SKILL.md +170 -170
- package/ftm-routine.yml +4 -4
- package/ftm-state/blackboard/capabilities.json +5 -5
- package/ftm-state/blackboard/capabilities.schema.json +27 -27
- package/ftm-state/blackboard/context.json +37 -23
- package/ftm-state/blackboard/experiences/doom-statusline-fix.json +26 -0
- package/ftm-state/blackboard/experiences/hackathon-pages-site.json +26 -0
- package/ftm-state/blackboard/experiences/hindsight-sso-kickoff.json +42 -0
- package/ftm-state/blackboard/experiences/index.json +58 -9
- package/ftm-state/blackboard/experiences/learning-ragnarok-api-access.json +23 -0
- package/ftm-state/blackboard/experiences/nordlayer-members-auto-assign.json +26 -0
- package/ftm-state/blackboard/experiences/saml2aws-stale-session-fix.json +41 -0
- package/ftm-state/blackboard/patterns.json +6 -6
- package/ftm-state/schemas/context.schema.json +130 -130
- package/ftm-state/schemas/experience-index.schema.json +77 -77
- package/ftm-state/schemas/experience.schema.json +78 -78
- package/ftm-state/schemas/patterns.schema.json +44 -44
- package/ftm-upgrade/SKILL.md +194 -194
- package/ftm-upgrade/scripts/check-version.sh +76 -76
- package/ftm-upgrade/scripts/upgrade.sh +143 -143
- package/ftm-upgrade.yml +2 -2
- package/ftm-verify.yml +2 -2
- package/ftm.yml +2 -2
- package/hooks/ftm-auto-log.sh +137 -0
- package/hooks/ftm-blackboard-enforcer.sh +93 -93
- package/hooks/ftm-discovery-reminder.sh +90 -90
- package/hooks/ftm-drafts-gate.sh +61 -61
- package/hooks/ftm-event-logger.mjs +107 -107
- package/hooks/ftm-install-hooks.sh +240 -0
- package/hooks/ftm-learning-capture.sh +117 -0
- package/hooks/ftm-map-autodetect.sh +79 -79
- package/hooks/ftm-pending-sync-check.sh +22 -22
- package/hooks/ftm-plan-gate.sh +92 -92
- package/hooks/ftm-post-commit-trigger.sh +57 -57
- package/hooks/ftm-post-compaction.sh +138 -0
- package/hooks/ftm-pre-compaction.sh +147 -0
- package/hooks/ftm-session-end.sh +52 -0
- package/hooks/ftm-session-snapshot.sh +213 -0
- package/hooks/settings-template.json +81 -81
- package/install.sh +363 -363
- package/package.json +84 -84
- package/uninstall.sh +25 -25
package/ftm-map/scripts/query.py
CHANGED
|
@@ -1,301 +1,301 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
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
|
-
"""
|
|
14
|
-
|
|
15
|
-
import argparse
|
|
16
|
-
import json
|
|
17
|
-
import os
|
|
18
|
-
import sys
|
|
19
|
-
|
|
20
|
-
sys.path.insert(0, os.path.dirname(__file__))
|
|
21
|
-
|
|
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
|
-
# ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def blast_radius(conn, symbol_name: str, max_depth: int = 10) -> dict:
|
|
38
|
-
"""Get all symbols that would be affected if this symbol changes."""
|
|
39
|
-
symbols = get_symbol_by_name(conn, symbol_name)
|
|
40
|
-
if not symbols:
|
|
41
|
-
return {"error": f"Symbol '{symbol_name}' not found", "results": []}
|
|
42
|
-
|
|
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
|
-
|
|
50
|
-
deps = get_reverse_deps(conn, sym["id"], max_depth)
|
|
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
|
-
|
|
66
|
-
return {
|
|
67
|
-
"symbol": symbol_name,
|
|
68
|
-
"symbol_file": file_path,
|
|
69
|
-
"affected_count": len(enriched),
|
|
70
|
-
"results": enriched,
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def dependency_chain(conn, symbol_name: str, max_depth: int = 10) -> dict:
|
|
75
|
-
"""Get all symbols this one depends on."""
|
|
76
|
-
symbols = get_symbol_by_name(conn, symbol_name)
|
|
77
|
-
if not symbols:
|
|
78
|
-
return {"error": f"Symbol '{symbol_name}' not found", "results": []}
|
|
79
|
-
|
|
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
|
-
|
|
86
|
-
deps = get_transitive_deps(conn, sym["id"], max_depth)
|
|
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
|
-
|
|
101
|
-
return {
|
|
102
|
-
"symbol": symbol_name,
|
|
103
|
-
"symbol_file": file_path,
|
|
104
|
-
"dependency_count": len(enriched),
|
|
105
|
-
"results": enriched,
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def search(conn, query_text: str, limit: int = 10) -> dict:
|
|
110
|
-
"""BM25-ranked full-text search."""
|
|
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
|
-
|
|
129
|
-
return {
|
|
130
|
-
"query": query_text,
|
|
131
|
-
"result_count": len(enriched),
|
|
132
|
-
"results": enriched,
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def symbol_info(conn, symbol_name: str) -> dict:
|
|
137
|
-
"""Full details about a symbol including callers, callees, and blast radius count."""
|
|
138
|
-
symbols = get_symbol_by_name(conn, symbol_name)
|
|
139
|
-
if not symbols:
|
|
140
|
-
return {"error": f"Symbol '{symbol_name}' not found"}
|
|
141
|
-
|
|
142
|
-
sym = symbols[0]
|
|
143
|
-
sym_id = sym["id"]
|
|
144
|
-
|
|
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
|
|
152
|
-
callers = conn.execute(
|
|
153
|
-
"""
|
|
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 = ?
|
|
159
|
-
""",
|
|
160
|
-
(sym_id,),
|
|
161
|
-
).fetchall()
|
|
162
|
-
|
|
163
|
-
# Direct callees (who I reference) via symbol_edges
|
|
164
|
-
callees = conn.execute(
|
|
165
|
-
"""
|
|
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 = ?
|
|
171
|
-
""",
|
|
172
|
-
(sym_id,),
|
|
173
|
-
).fetchall()
|
|
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
|
-
|
|
180
|
-
# Blast radius count
|
|
181
|
-
blast = get_reverse_deps(conn, sym_id)
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
"name": sym["name"],
|
|
185
|
-
"qualified_name": sym.get("qualified_name", ""),
|
|
186
|
-
"kind": sym["kind"],
|
|
187
|
-
"file": file_path,
|
|
188
|
-
"line_start": sym["line_start"],
|
|
189
|
-
"line_end": sym.get("line_end"),
|
|
190
|
-
"signature": sym.get("signature", ""),
|
|
191
|
-
"callers": [dict(r) for r in callers],
|
|
192
|
-
"callees": [dict(r) for r in callees],
|
|
193
|
-
"reference_count": ref_count,
|
|
194
|
-
"blast_radius_count": len(blast),
|
|
195
|
-
}
|
|
196
|
-
|
|
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
|
-
|
|
232
|
-
def main():
|
|
233
|
-
parser = argparse.ArgumentParser(description="ftm-map query interface")
|
|
234
|
-
parser.add_argument(
|
|
235
|
-
"--blast-radius", metavar="SYMBOL", help="Show blast radius for a symbol"
|
|
236
|
-
)
|
|
237
|
-
parser.add_argument(
|
|
238
|
-
"--deps", metavar="SYMBOL", help="Show dependency chain for a symbol"
|
|
239
|
-
)
|
|
240
|
-
parser.add_argument("--search", metavar="QUERY", help="Full-text search")
|
|
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
|
-
)
|
|
260
|
-
parser.add_argument(
|
|
261
|
-
"--limit", type=int, default=10, help="Result limit for search"
|
|
262
|
-
)
|
|
263
|
-
parser.add_argument(
|
|
264
|
-
"--max-depth", type=int, default=10, help="Max traversal depth"
|
|
265
|
-
)
|
|
266
|
-
parser.add_argument(
|
|
267
|
-
"--project-root",
|
|
268
|
-
default=os.getcwd(),
|
|
269
|
-
help="Project root directory",
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
args = parser.parse_args()
|
|
273
|
-
|
|
274
|
-
conn = get_connection(args.project_root)
|
|
275
|
-
try:
|
|
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:
|
|
284
|
-
result = blast_radius(conn, args.blast_radius, args.max_depth)
|
|
285
|
-
elif args.deps:
|
|
286
|
-
result = dependency_chain(conn, args.deps, args.max_depth)
|
|
287
|
-
elif args.search:
|
|
288
|
-
result = search(conn, args.search, args.limit)
|
|
289
|
-
elif args.info:
|
|
290
|
-
result = symbol_info(conn, args.info)
|
|
291
|
-
else:
|
|
292
|
-
parser.print_help()
|
|
293
|
-
sys.exit(1)
|
|
294
|
-
|
|
295
|
-
print(json.dumps(result, indent=2, default=str))
|
|
296
|
-
finally:
|
|
297
|
-
conn.close()
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if __name__ == "__main__":
|
|
301
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
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
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
21
|
+
|
|
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
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def blast_radius(conn, symbol_name: str, max_depth: int = 10) -> dict:
|
|
38
|
+
"""Get all symbols that would be affected if this symbol changes."""
|
|
39
|
+
symbols = get_symbol_by_name(conn, symbol_name)
|
|
40
|
+
if not symbols:
|
|
41
|
+
return {"error": f"Symbol '{symbol_name}' not found", "results": []}
|
|
42
|
+
|
|
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
|
+
|
|
50
|
+
deps = get_reverse_deps(conn, sym["id"], max_depth)
|
|
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
|
+
|
|
66
|
+
return {
|
|
67
|
+
"symbol": symbol_name,
|
|
68
|
+
"symbol_file": file_path,
|
|
69
|
+
"affected_count": len(enriched),
|
|
70
|
+
"results": enriched,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def dependency_chain(conn, symbol_name: str, max_depth: int = 10) -> dict:
|
|
75
|
+
"""Get all symbols this one depends on."""
|
|
76
|
+
symbols = get_symbol_by_name(conn, symbol_name)
|
|
77
|
+
if not symbols:
|
|
78
|
+
return {"error": f"Symbol '{symbol_name}' not found", "results": []}
|
|
79
|
+
|
|
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
|
+
|
|
86
|
+
deps = get_transitive_deps(conn, sym["id"], max_depth)
|
|
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
|
+
|
|
101
|
+
return {
|
|
102
|
+
"symbol": symbol_name,
|
|
103
|
+
"symbol_file": file_path,
|
|
104
|
+
"dependency_count": len(enriched),
|
|
105
|
+
"results": enriched,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def search(conn, query_text: str, limit: int = 10) -> dict:
|
|
110
|
+
"""BM25-ranked full-text search."""
|
|
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
|
+
|
|
129
|
+
return {
|
|
130
|
+
"query": query_text,
|
|
131
|
+
"result_count": len(enriched),
|
|
132
|
+
"results": enriched,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def symbol_info(conn, symbol_name: str) -> dict:
|
|
137
|
+
"""Full details about a symbol including callers, callees, and blast radius count."""
|
|
138
|
+
symbols = get_symbol_by_name(conn, symbol_name)
|
|
139
|
+
if not symbols:
|
|
140
|
+
return {"error": f"Symbol '{symbol_name}' not found"}
|
|
141
|
+
|
|
142
|
+
sym = symbols[0]
|
|
143
|
+
sym_id = sym["id"]
|
|
144
|
+
|
|
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
|
|
152
|
+
callers = conn.execute(
|
|
153
|
+
"""
|
|
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 = ?
|
|
159
|
+
""",
|
|
160
|
+
(sym_id,),
|
|
161
|
+
).fetchall()
|
|
162
|
+
|
|
163
|
+
# Direct callees (who I reference) via symbol_edges
|
|
164
|
+
callees = conn.execute(
|
|
165
|
+
"""
|
|
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 = ?
|
|
171
|
+
""",
|
|
172
|
+
(sym_id,),
|
|
173
|
+
).fetchall()
|
|
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
|
+
|
|
180
|
+
# Blast radius count
|
|
181
|
+
blast = get_reverse_deps(conn, sym_id)
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
"name": sym["name"],
|
|
185
|
+
"qualified_name": sym.get("qualified_name", ""),
|
|
186
|
+
"kind": sym["kind"],
|
|
187
|
+
"file": file_path,
|
|
188
|
+
"line_start": sym["line_start"],
|
|
189
|
+
"line_end": sym.get("line_end"),
|
|
190
|
+
"signature": sym.get("signature", ""),
|
|
191
|
+
"callers": [dict(r) for r in callers],
|
|
192
|
+
"callees": [dict(r) for r in callees],
|
|
193
|
+
"reference_count": ref_count,
|
|
194
|
+
"blast_radius_count": len(blast),
|
|
195
|
+
}
|
|
196
|
+
|
|
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
|
+
|
|
232
|
+
def main():
|
|
233
|
+
parser = argparse.ArgumentParser(description="ftm-map query interface")
|
|
234
|
+
parser.add_argument(
|
|
235
|
+
"--blast-radius", metavar="SYMBOL", help="Show blast radius for a symbol"
|
|
236
|
+
)
|
|
237
|
+
parser.add_argument(
|
|
238
|
+
"--deps", metavar="SYMBOL", help="Show dependency chain for a symbol"
|
|
239
|
+
)
|
|
240
|
+
parser.add_argument("--search", metavar="QUERY", help="Full-text search")
|
|
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
|
+
)
|
|
260
|
+
parser.add_argument(
|
|
261
|
+
"--limit", type=int, default=10, help="Result limit for search"
|
|
262
|
+
)
|
|
263
|
+
parser.add_argument(
|
|
264
|
+
"--max-depth", type=int, default=10, help="Max traversal depth"
|
|
265
|
+
)
|
|
266
|
+
parser.add_argument(
|
|
267
|
+
"--project-root",
|
|
268
|
+
default=os.getcwd(),
|
|
269
|
+
help="Project root directory",
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
args = parser.parse_args()
|
|
273
|
+
|
|
274
|
+
conn = get_connection(args.project_root)
|
|
275
|
+
try:
|
|
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:
|
|
284
|
+
result = blast_radius(conn, args.blast_radius, args.max_depth)
|
|
285
|
+
elif args.deps:
|
|
286
|
+
result = dependency_chain(conn, args.deps, args.max_depth)
|
|
287
|
+
elif args.search:
|
|
288
|
+
result = search(conn, args.search, args.limit)
|
|
289
|
+
elif args.info:
|
|
290
|
+
result = symbol_info(conn, args.info)
|
|
291
|
+
else:
|
|
292
|
+
parser.print_help()
|
|
293
|
+
sys.exit(1)
|
|
294
|
+
|
|
295
|
+
print(json.dumps(result, indent=2, default=str))
|
|
296
|
+
finally:
|
|
297
|
+
conn.close()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
if __name__ == "__main__":
|
|
301
|
+
main()
|