feed-the-machine 1.5.0 → 1.6.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/generate-manifest.mjs +463 -463
- package/bin/install.mjs +491 -491
- package/docs/HOOKS.md +243 -243
- package/docs/INBOX.md +233 -233
- package/ftm/SKILL.md +122 -122
- package/ftm-audit/SKILL.md +623 -541
- 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 +498 -498
- package/ftm-brainstorm/evals/evals.json +100 -100
- package/ftm-brainstorm/evals/promptfoo.yaml +109 -109
- package/ftm-brainstorm/references/agent-prompts.md +224 -224
- package/ftm-brainstorm/references/plan-template.md +121 -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 +345 -345
- package/ftm-config.default.yml +82 -80
- package/ftm-config.yml +2 -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 -767
- 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 -44
- 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/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/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/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 +1943 -1943
- package/ftm-mind/evals/promptfoo.yaml +142 -142
- package/ftm-mind/references/blackboard-schema.md +328 -328
- package/ftm-mind/references/complexity-guide.md +110 -110
- package/ftm-mind/references/event-registry.md +319 -319
- package/ftm-mind/references/mcp-inventory.md +296 -296
- 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-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 +23 -23
- package/ftm-state/blackboard/experiences/index.json +9 -9
- 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-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-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/settings-template.json +81 -81
- package/install.sh +363 -363
- package/package.json +84 -84
- package/uninstall.sh +25 -25
|
@@ -1,224 +1,224 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Tree-sitter based parser for extracting definition and reference tags from source code.
|
|
3
|
-
|
|
4
|
-
Uses Aider-style @name.definition.* / @name.reference.* capture convention in
|
|
5
|
-
per-language .scm query files for structured tag extraction. Falls back to Pygments
|
|
6
|
-
lexer for reference extraction when tree-sitter queries lack reference patterns.
|
|
7
|
-
"""
|
|
8
|
-
import hashlib
|
|
9
|
-
import os
|
|
10
|
-
import sys
|
|
11
|
-
from collections import namedtuple
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Optional
|
|
14
|
-
|
|
15
|
-
import tree_sitter as ts
|
|
16
|
-
from tree_sitter_language_pack import get_language, get_parser
|
|
17
|
-
|
|
18
|
-
# Tag namedtuple: the single output type for all extraction
|
|
19
|
-
# kind is "def" or "ref"
|
|
20
|
-
# rel_fname is relative path, fname is absolute path
|
|
21
|
-
Tag = namedtuple("Tag", ["rel_fname", "fname", "line", "name", "kind"])
|
|
22
|
-
|
|
23
|
-
QUERIES_DIR = os.path.join(os.path.dirname(__file__), "queries")
|
|
24
|
-
|
|
25
|
-
# Map file extensions to tree-sitter language names
|
|
26
|
-
EXTENSION_MAP = {
|
|
27
|
-
".ts": "typescript",
|
|
28
|
-
".tsx": "tsx",
|
|
29
|
-
".js": "javascript",
|
|
30
|
-
".jsx": "javascript",
|
|
31
|
-
".py": "python",
|
|
32
|
-
".rs": "rust",
|
|
33
|
-
".go": "go",
|
|
34
|
-
".rb": "ruby",
|
|
35
|
-
".java": "java",
|
|
36
|
-
".swift": "swift",
|
|
37
|
-
".kt": "kotlin",
|
|
38
|
-
".c": "c",
|
|
39
|
-
".cpp": "cpp",
|
|
40
|
-
".h": "c",
|
|
41
|
-
".hpp": "cpp",
|
|
42
|
-
".cs": "c_sharp",
|
|
43
|
-
".sh": "bash",
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# ---------------------------------------------------------------------------
|
|
48
|
-
# Public API
|
|
49
|
-
# ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
def detect_language(file_path: str) -> Optional[str]:
|
|
52
|
-
"""Detect tree-sitter language from file extension."""
|
|
53
|
-
ext = Path(file_path).suffix.lower()
|
|
54
|
-
return EXTENSION_MAP.get(ext)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def compute_content_hash(content: str) -> str:
|
|
58
|
-
"""Compute a short SHA-256 hash of content for change detection."""
|
|
59
|
-
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def get_tags(fname: str, rel_fname: str = None) -> list[Tag]:
|
|
63
|
-
"""Extract definition and reference tags from a source file.
|
|
64
|
-
|
|
65
|
-
Uses tree-sitter query files with @name.definition.* and @name.reference.*
|
|
66
|
-
capture naming convention (Aider-style).
|
|
67
|
-
|
|
68
|
-
Returns list of Tag namedtuples with kind="def" or kind="ref".
|
|
69
|
-
"""
|
|
70
|
-
if rel_fname is None:
|
|
71
|
-
rel_fname = fname
|
|
72
|
-
|
|
73
|
-
lang = detect_language(fname)
|
|
74
|
-
if not lang:
|
|
75
|
-
return []
|
|
76
|
-
|
|
77
|
-
source = _read_source(fname)
|
|
78
|
-
if source is None:
|
|
79
|
-
return []
|
|
80
|
-
|
|
81
|
-
tree = _parse_source(source, lang, fname)
|
|
82
|
-
if tree is None:
|
|
83
|
-
return []
|
|
84
|
-
|
|
85
|
-
scm_path = os.path.join(QUERIES_DIR, f"{lang}-tags.scm")
|
|
86
|
-
if not os.path.exists(scm_path):
|
|
87
|
-
# No query file -- use Pygments fallback for refs only
|
|
88
|
-
return _pygments_ref_fallback(source, fname, rel_fname)
|
|
89
|
-
|
|
90
|
-
tags = _extract_tags(tree, source, fname, rel_fname, lang, scm_path)
|
|
91
|
-
|
|
92
|
-
# If we got defs but no refs, supplement with Pygments fallback for refs
|
|
93
|
-
has_defs = any(t.kind == "def" for t in tags)
|
|
94
|
-
has_refs = any(t.kind == "ref" for t in tags)
|
|
95
|
-
if has_defs and not has_refs:
|
|
96
|
-
ref_tags = _pygments_ref_fallback(source, fname, rel_fname)
|
|
97
|
-
tags.extend(ref_tags)
|
|
98
|
-
|
|
99
|
-
return tags
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# ---------------------------------------------------------------------------
|
|
103
|
-
# Internal helpers
|
|
104
|
-
# ---------------------------------------------------------------------------
|
|
105
|
-
|
|
106
|
-
def _read_source(file_path: str) -> Optional[str]:
|
|
107
|
-
"""Read a source file, returning None on IO error."""
|
|
108
|
-
try:
|
|
109
|
-
with open(file_path, "r", encoding="utf-8", errors="replace") as fh:
|
|
110
|
-
return fh.read()
|
|
111
|
-
except (IOError, OSError) as exc:
|
|
112
|
-
print(f"Warning: Cannot read {file_path}: {exc}", file=sys.stderr)
|
|
113
|
-
return None
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def _parse_source(source: str, lang: str, file_path: str):
|
|
117
|
-
"""Parse source text with tree-sitter, returning None on error."""
|
|
118
|
-
try:
|
|
119
|
-
parser = get_parser(lang)
|
|
120
|
-
return parser.parse(source.encode())
|
|
121
|
-
except Exception as exc: # noqa: BLE001
|
|
122
|
-
print(f"Warning: Parse error for {file_path}: {exc}", file=sys.stderr)
|
|
123
|
-
return None
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
# ---------------------------------------------------------------------------
|
|
127
|
-
# Tag extraction via tree-sitter queries
|
|
128
|
-
# ---------------------------------------------------------------------------
|
|
129
|
-
|
|
130
|
-
def _extract_tags(tree, source: str, fname: str, rel_fname: str, lang: str, scm_path: str) -> list[Tag]:
|
|
131
|
-
"""Extract tags using tree-sitter query with @name.definition.* / @name.reference.* convention."""
|
|
132
|
-
try:
|
|
133
|
-
with open(scm_path) as fh:
|
|
134
|
-
query_text = fh.read()
|
|
135
|
-
except (IOError, OSError):
|
|
136
|
-
return []
|
|
137
|
-
|
|
138
|
-
try:
|
|
139
|
-
language = get_language(lang)
|
|
140
|
-
query = ts.Query(language, query_text)
|
|
141
|
-
cursor = ts.QueryCursor(query)
|
|
142
|
-
matches = list(cursor.matches(tree.root_node))
|
|
143
|
-
except Exception: # noqa: BLE001
|
|
144
|
-
return []
|
|
145
|
-
|
|
146
|
-
tags = []
|
|
147
|
-
seen = {} # (name, start_byte, end_byte) -> Tag for dedup
|
|
148
|
-
|
|
149
|
-
for _pattern_idx, capture_dict in matches:
|
|
150
|
-
for capture_name, nodes in capture_dict.items():
|
|
151
|
-
# Only process @name.definition.* and @name.reference.* captures
|
|
152
|
-
if capture_name.startswith("name.definition."):
|
|
153
|
-
kind = "def"
|
|
154
|
-
elif capture_name.startswith("name.reference."):
|
|
155
|
-
kind = "ref"
|
|
156
|
-
else:
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
for node in nodes:
|
|
160
|
-
name_text = source[node.start_byte:node.end_byte].strip()
|
|
161
|
-
if not name_text:
|
|
162
|
-
continue
|
|
163
|
-
|
|
164
|
-
key = (name_text, node.start_byte, node.end_byte)
|
|
165
|
-
if key in seen:
|
|
166
|
-
continue
|
|
167
|
-
|
|
168
|
-
line = node.start_point[0] + 1
|
|
169
|
-
tag = Tag(rel_fname=rel_fname, fname=fname, line=line, name=name_text, kind=kind)
|
|
170
|
-
seen[key] = tag
|
|
171
|
-
tags.append(tag)
|
|
172
|
-
|
|
173
|
-
return tags
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# ---------------------------------------------------------------------------
|
|
177
|
-
# Pygments fallback for references
|
|
178
|
-
# ---------------------------------------------------------------------------
|
|
179
|
-
|
|
180
|
-
def _pygments_ref_fallback(source: str, fname: str, rel_fname: str) -> list[Tag]:
|
|
181
|
-
"""Use Pygments to extract reference-like tokens when tree-sitter refs are missing."""
|
|
182
|
-
try:
|
|
183
|
-
from pygments.lexers import get_lexer_for_filename
|
|
184
|
-
from pygments.token import Token
|
|
185
|
-
except ImportError:
|
|
186
|
-
return []
|
|
187
|
-
|
|
188
|
-
try:
|
|
189
|
-
lexer = get_lexer_for_filename(fname)
|
|
190
|
-
except Exception: # noqa: BLE001
|
|
191
|
-
return []
|
|
192
|
-
|
|
193
|
-
tags = []
|
|
194
|
-
line = 1
|
|
195
|
-
for token_type, value in lexer.get_tokens(source):
|
|
196
|
-
# Count newlines for line tracking
|
|
197
|
-
newlines = value.count('\n')
|
|
198
|
-
if token_type in Token.Name and value.strip():
|
|
199
|
-
tags.append(Tag(rel_fname=rel_fname, fname=fname, line=line, name=value.strip(), kind="ref"))
|
|
200
|
-
line += newlines
|
|
201
|
-
|
|
202
|
-
return tags
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# ---------------------------------------------------------------------------
|
|
206
|
-
# Small utilities (kept for potential downstream use)
|
|
207
|
-
# ---------------------------------------------------------------------------
|
|
208
|
-
|
|
209
|
-
def _first_line(text: str, max_len: int = 200) -> str:
|
|
210
|
-
"""Return the first non-empty line of text, truncated to max_len."""
|
|
211
|
-
line = text.split("\n")[0].strip()
|
|
212
|
-
return line[:max_len] + "..." if len(line) > max_len else line
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def _find_doc_comment(node, source: str) -> str:
|
|
216
|
-
"""Try to extract a doc comment from the node's previous sibling."""
|
|
217
|
-
prev = node.prev_named_sibling
|
|
218
|
-
if prev and prev.type in ("comment", "block_comment", "string", "string_literal"):
|
|
219
|
-
text = source[prev.start_byte:prev.end_byte].strip()
|
|
220
|
-
# Strip common comment markers
|
|
221
|
-
for marker in ("///", "/**", "/*", "*/", "//", "#", '"""', "'''"):
|
|
222
|
-
text = text.strip(marker)
|
|
223
|
-
return text.strip()[:500]
|
|
224
|
-
return ""
|
|
1
|
+
"""
|
|
2
|
+
Tree-sitter based parser for extracting definition and reference tags from source code.
|
|
3
|
+
|
|
4
|
+
Uses Aider-style @name.definition.* / @name.reference.* capture convention in
|
|
5
|
+
per-language .scm query files for structured tag extraction. Falls back to Pygments
|
|
6
|
+
lexer for reference extraction when tree-sitter queries lack reference patterns.
|
|
7
|
+
"""
|
|
8
|
+
import hashlib
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from collections import namedtuple
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
import tree_sitter as ts
|
|
16
|
+
from tree_sitter_language_pack import get_language, get_parser
|
|
17
|
+
|
|
18
|
+
# Tag namedtuple: the single output type for all extraction
|
|
19
|
+
# kind is "def" or "ref"
|
|
20
|
+
# rel_fname is relative path, fname is absolute path
|
|
21
|
+
Tag = namedtuple("Tag", ["rel_fname", "fname", "line", "name", "kind"])
|
|
22
|
+
|
|
23
|
+
QUERIES_DIR = os.path.join(os.path.dirname(__file__), "queries")
|
|
24
|
+
|
|
25
|
+
# Map file extensions to tree-sitter language names
|
|
26
|
+
EXTENSION_MAP = {
|
|
27
|
+
".ts": "typescript",
|
|
28
|
+
".tsx": "tsx",
|
|
29
|
+
".js": "javascript",
|
|
30
|
+
".jsx": "javascript",
|
|
31
|
+
".py": "python",
|
|
32
|
+
".rs": "rust",
|
|
33
|
+
".go": "go",
|
|
34
|
+
".rb": "ruby",
|
|
35
|
+
".java": "java",
|
|
36
|
+
".swift": "swift",
|
|
37
|
+
".kt": "kotlin",
|
|
38
|
+
".c": "c",
|
|
39
|
+
".cpp": "cpp",
|
|
40
|
+
".h": "c",
|
|
41
|
+
".hpp": "cpp",
|
|
42
|
+
".cs": "c_sharp",
|
|
43
|
+
".sh": "bash",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Public API
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
def detect_language(file_path: str) -> Optional[str]:
|
|
52
|
+
"""Detect tree-sitter language from file extension."""
|
|
53
|
+
ext = Path(file_path).suffix.lower()
|
|
54
|
+
return EXTENSION_MAP.get(ext)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def compute_content_hash(content: str) -> str:
|
|
58
|
+
"""Compute a short SHA-256 hash of content for change detection."""
|
|
59
|
+
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_tags(fname: str, rel_fname: str = None) -> list[Tag]:
|
|
63
|
+
"""Extract definition and reference tags from a source file.
|
|
64
|
+
|
|
65
|
+
Uses tree-sitter query files with @name.definition.* and @name.reference.*
|
|
66
|
+
capture naming convention (Aider-style).
|
|
67
|
+
|
|
68
|
+
Returns list of Tag namedtuples with kind="def" or kind="ref".
|
|
69
|
+
"""
|
|
70
|
+
if rel_fname is None:
|
|
71
|
+
rel_fname = fname
|
|
72
|
+
|
|
73
|
+
lang = detect_language(fname)
|
|
74
|
+
if not lang:
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
source = _read_source(fname)
|
|
78
|
+
if source is None:
|
|
79
|
+
return []
|
|
80
|
+
|
|
81
|
+
tree = _parse_source(source, lang, fname)
|
|
82
|
+
if tree is None:
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
scm_path = os.path.join(QUERIES_DIR, f"{lang}-tags.scm")
|
|
86
|
+
if not os.path.exists(scm_path):
|
|
87
|
+
# No query file -- use Pygments fallback for refs only
|
|
88
|
+
return _pygments_ref_fallback(source, fname, rel_fname)
|
|
89
|
+
|
|
90
|
+
tags = _extract_tags(tree, source, fname, rel_fname, lang, scm_path)
|
|
91
|
+
|
|
92
|
+
# If we got defs but no refs, supplement with Pygments fallback for refs
|
|
93
|
+
has_defs = any(t.kind == "def" for t in tags)
|
|
94
|
+
has_refs = any(t.kind == "ref" for t in tags)
|
|
95
|
+
if has_defs and not has_refs:
|
|
96
|
+
ref_tags = _pygments_ref_fallback(source, fname, rel_fname)
|
|
97
|
+
tags.extend(ref_tags)
|
|
98
|
+
|
|
99
|
+
return tags
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# Internal helpers
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
def _read_source(file_path: str) -> Optional[str]:
|
|
107
|
+
"""Read a source file, returning None on IO error."""
|
|
108
|
+
try:
|
|
109
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as fh:
|
|
110
|
+
return fh.read()
|
|
111
|
+
except (IOError, OSError) as exc:
|
|
112
|
+
print(f"Warning: Cannot read {file_path}: {exc}", file=sys.stderr)
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _parse_source(source: str, lang: str, file_path: str):
|
|
117
|
+
"""Parse source text with tree-sitter, returning None on error."""
|
|
118
|
+
try:
|
|
119
|
+
parser = get_parser(lang)
|
|
120
|
+
return parser.parse(source.encode())
|
|
121
|
+
except Exception as exc: # noqa: BLE001
|
|
122
|
+
print(f"Warning: Parse error for {file_path}: {exc}", file=sys.stderr)
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Tag extraction via tree-sitter queries
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
def _extract_tags(tree, source: str, fname: str, rel_fname: str, lang: str, scm_path: str) -> list[Tag]:
|
|
131
|
+
"""Extract tags using tree-sitter query with @name.definition.* / @name.reference.* convention."""
|
|
132
|
+
try:
|
|
133
|
+
with open(scm_path) as fh:
|
|
134
|
+
query_text = fh.read()
|
|
135
|
+
except (IOError, OSError):
|
|
136
|
+
return []
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
language = get_language(lang)
|
|
140
|
+
query = ts.Query(language, query_text)
|
|
141
|
+
cursor = ts.QueryCursor(query)
|
|
142
|
+
matches = list(cursor.matches(tree.root_node))
|
|
143
|
+
except Exception: # noqa: BLE001
|
|
144
|
+
return []
|
|
145
|
+
|
|
146
|
+
tags = []
|
|
147
|
+
seen = {} # (name, start_byte, end_byte) -> Tag for dedup
|
|
148
|
+
|
|
149
|
+
for _pattern_idx, capture_dict in matches:
|
|
150
|
+
for capture_name, nodes in capture_dict.items():
|
|
151
|
+
# Only process @name.definition.* and @name.reference.* captures
|
|
152
|
+
if capture_name.startswith("name.definition."):
|
|
153
|
+
kind = "def"
|
|
154
|
+
elif capture_name.startswith("name.reference."):
|
|
155
|
+
kind = "ref"
|
|
156
|
+
else:
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
for node in nodes:
|
|
160
|
+
name_text = source[node.start_byte:node.end_byte].strip()
|
|
161
|
+
if not name_text:
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
key = (name_text, node.start_byte, node.end_byte)
|
|
165
|
+
if key in seen:
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
line = node.start_point[0] + 1
|
|
169
|
+
tag = Tag(rel_fname=rel_fname, fname=fname, line=line, name=name_text, kind=kind)
|
|
170
|
+
seen[key] = tag
|
|
171
|
+
tags.append(tag)
|
|
172
|
+
|
|
173
|
+
return tags
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# Pygments fallback for references
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
def _pygments_ref_fallback(source: str, fname: str, rel_fname: str) -> list[Tag]:
|
|
181
|
+
"""Use Pygments to extract reference-like tokens when tree-sitter refs are missing."""
|
|
182
|
+
try:
|
|
183
|
+
from pygments.lexers import get_lexer_for_filename
|
|
184
|
+
from pygments.token import Token
|
|
185
|
+
except ImportError:
|
|
186
|
+
return []
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
lexer = get_lexer_for_filename(fname)
|
|
190
|
+
except Exception: # noqa: BLE001
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
tags = []
|
|
194
|
+
line = 1
|
|
195
|
+
for token_type, value in lexer.get_tokens(source):
|
|
196
|
+
# Count newlines for line tracking
|
|
197
|
+
newlines = value.count('\n')
|
|
198
|
+
if token_type in Token.Name and value.strip():
|
|
199
|
+
tags.append(Tag(rel_fname=rel_fname, fname=fname, line=line, name=value.strip(), kind="ref"))
|
|
200
|
+
line += newlines
|
|
201
|
+
|
|
202
|
+
return tags
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
# Small utilities (kept for potential downstream use)
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
def _first_line(text: str, max_len: int = 200) -> str:
|
|
210
|
+
"""Return the first non-empty line of text, truncated to max_len."""
|
|
211
|
+
line = text.split("\n")[0].strip()
|
|
212
|
+
return line[:max_len] + "..." if len(line) > max_len else line
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _find_doc_comment(node, source: str) -> str:
|
|
216
|
+
"""Try to extract a doc comment from the node's previous sibling."""
|
|
217
|
+
prev = node.prev_named_sibling
|
|
218
|
+
if prev and prev.type in ("comment", "block_comment", "string", "string_literal"):
|
|
219
|
+
text = source[prev.start_byte:prev.end_byte].strip()
|
|
220
|
+
# Strip common comment markers
|
|
221
|
+
for marker in ("///", "/**", "/*", "*/", "//", "#", '"""', "'''"):
|
|
222
|
+
text = text.strip(marker)
|
|
223
|
+
return text.strip()[:500]
|
|
224
|
+
return ""
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
; Functions
|
|
2
|
-
(function_declaration
|
|
3
|
-
name: (identifier) @name.definition.function) @definition.function
|
|
4
|
-
|
|
5
|
-
; Methods
|
|
6
|
-
(method_declaration
|
|
7
|
-
name: (field_identifier) @name.definition.method) @definition.method
|
|
8
|
-
|
|
9
|
-
; Type declarations
|
|
10
|
-
(type_declaration
|
|
11
|
-
(type_spec
|
|
12
|
-
name: (type_identifier) @name.definition.type)) @definition.type
|
|
13
|
-
|
|
14
|
-
; Call references
|
|
15
|
-
(call_expression
|
|
16
|
-
function: [
|
|
17
|
-
(identifier) @name.reference.call
|
|
18
|
-
(selector_expression
|
|
19
|
-
field: (field_identifier) @name.reference.call)
|
|
20
|
-
]) @reference.call
|
|
1
|
+
; Functions
|
|
2
|
+
(function_declaration
|
|
3
|
+
name: (identifier) @name.definition.function) @definition.function
|
|
4
|
+
|
|
5
|
+
; Methods
|
|
6
|
+
(method_declaration
|
|
7
|
+
name: (field_identifier) @name.definition.method) @definition.method
|
|
8
|
+
|
|
9
|
+
; Type declarations
|
|
10
|
+
(type_declaration
|
|
11
|
+
(type_spec
|
|
12
|
+
name: (type_identifier) @name.definition.type)) @definition.type
|
|
13
|
+
|
|
14
|
+
; Call references
|
|
15
|
+
(call_expression
|
|
16
|
+
function: [
|
|
17
|
+
(identifier) @name.reference.call
|
|
18
|
+
(selector_expression
|
|
19
|
+
field: (field_identifier) @name.reference.call)
|
|
20
|
+
]) @reference.call
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
; Functions
|
|
2
|
-
(function_declaration
|
|
3
|
-
name: (identifier) @name.definition.function) @definition.function
|
|
4
|
-
|
|
5
|
-
; Methods
|
|
6
|
-
(method_definition
|
|
7
|
-
name: (property_identifier) @name.definition.method) @definition.method
|
|
8
|
-
|
|
9
|
-
; Classes
|
|
10
|
-
(class_declaration
|
|
11
|
-
name: (identifier) @name.definition.class) @definition.class
|
|
12
|
-
|
|
13
|
-
; Arrow functions assigned to const/let
|
|
14
|
-
(lexical_declaration
|
|
15
|
-
(variable_declarator
|
|
16
|
-
name: (identifier) @name.definition.function
|
|
17
|
-
value: (arrow_function))) @definition.function
|
|
18
|
-
|
|
19
|
-
; Arrow functions assigned to var
|
|
20
|
-
(variable_declaration
|
|
21
|
-
(variable_declarator
|
|
22
|
-
name: (identifier) @name.definition.function
|
|
23
|
-
value: (arrow_function))) @definition.function
|
|
24
|
-
|
|
25
|
-
; Call references
|
|
26
|
-
(call_expression
|
|
27
|
-
function: [
|
|
28
|
-
(identifier) @name.reference.call
|
|
29
|
-
(member_expression
|
|
30
|
-
property: (property_identifier) @name.reference.call)
|
|
31
|
-
]) @reference.call
|
|
32
|
-
|
|
33
|
-
; New expressions
|
|
34
|
-
(new_expression
|
|
35
|
-
constructor: (identifier) @name.reference.class) @reference.class
|
|
1
|
+
; Functions
|
|
2
|
+
(function_declaration
|
|
3
|
+
name: (identifier) @name.definition.function) @definition.function
|
|
4
|
+
|
|
5
|
+
; Methods
|
|
6
|
+
(method_definition
|
|
7
|
+
name: (property_identifier) @name.definition.method) @definition.method
|
|
8
|
+
|
|
9
|
+
; Classes
|
|
10
|
+
(class_declaration
|
|
11
|
+
name: (identifier) @name.definition.class) @definition.class
|
|
12
|
+
|
|
13
|
+
; Arrow functions assigned to const/let
|
|
14
|
+
(lexical_declaration
|
|
15
|
+
(variable_declarator
|
|
16
|
+
name: (identifier) @name.definition.function
|
|
17
|
+
value: (arrow_function))) @definition.function
|
|
18
|
+
|
|
19
|
+
; Arrow functions assigned to var
|
|
20
|
+
(variable_declaration
|
|
21
|
+
(variable_declarator
|
|
22
|
+
name: (identifier) @name.definition.function
|
|
23
|
+
value: (arrow_function))) @definition.function
|
|
24
|
+
|
|
25
|
+
; Call references
|
|
26
|
+
(call_expression
|
|
27
|
+
function: [
|
|
28
|
+
(identifier) @name.reference.call
|
|
29
|
+
(member_expression
|
|
30
|
+
property: (property_identifier) @name.reference.call)
|
|
31
|
+
]) @reference.call
|
|
32
|
+
|
|
33
|
+
; New expressions
|
|
34
|
+
(new_expression
|
|
35
|
+
constructor: (identifier) @name.reference.class) @reference.class
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
; Module-level constants
|
|
2
|
-
(module
|
|
3
|
-
(expression_statement
|
|
4
|
-
(assignment
|
|
5
|
-
left: (identifier) @name.definition.constant) @definition.constant))
|
|
6
|
-
|
|
7
|
-
; Classes
|
|
8
|
-
(class_definition
|
|
9
|
-
name: (identifier) @name.definition.class) @definition.class
|
|
10
|
-
|
|
11
|
-
; Functions
|
|
12
|
-
(function_definition
|
|
13
|
-
name: (identifier) @name.definition.function) @definition.function
|
|
14
|
-
|
|
15
|
-
; Decorated definitions (functions)
|
|
16
|
-
(decorated_definition
|
|
17
|
-
definition: (function_definition
|
|
18
|
-
name: (identifier) @name.definition.function)) @definition.function
|
|
19
|
-
|
|
20
|
-
; Decorated definitions (classes)
|
|
21
|
-
(decorated_definition
|
|
22
|
-
definition: (class_definition
|
|
23
|
-
name: (identifier) @name.definition.class)) @definition.class
|
|
24
|
-
|
|
25
|
-
; Call references (direct function calls and attribute method calls)
|
|
26
|
-
(call
|
|
27
|
-
function: [
|
|
28
|
-
(identifier) @name.reference.call
|
|
29
|
-
(attribute
|
|
30
|
-
attribute: (identifier) @name.reference.call)
|
|
31
|
-
]) @reference.call
|
|
1
|
+
; Module-level constants
|
|
2
|
+
(module
|
|
3
|
+
(expression_statement
|
|
4
|
+
(assignment
|
|
5
|
+
left: (identifier) @name.definition.constant) @definition.constant))
|
|
6
|
+
|
|
7
|
+
; Classes
|
|
8
|
+
(class_definition
|
|
9
|
+
name: (identifier) @name.definition.class) @definition.class
|
|
10
|
+
|
|
11
|
+
; Functions
|
|
12
|
+
(function_definition
|
|
13
|
+
name: (identifier) @name.definition.function) @definition.function
|
|
14
|
+
|
|
15
|
+
; Decorated definitions (functions)
|
|
16
|
+
(decorated_definition
|
|
17
|
+
definition: (function_definition
|
|
18
|
+
name: (identifier) @name.definition.function)) @definition.function
|
|
19
|
+
|
|
20
|
+
; Decorated definitions (classes)
|
|
21
|
+
(decorated_definition
|
|
22
|
+
definition: (class_definition
|
|
23
|
+
name: (identifier) @name.definition.class)) @definition.class
|
|
24
|
+
|
|
25
|
+
; Call references (direct function calls and attribute method calls)
|
|
26
|
+
(call
|
|
27
|
+
function: [
|
|
28
|
+
(identifier) @name.reference.call
|
|
29
|
+
(attribute
|
|
30
|
+
attribute: (identifier) @name.reference.call)
|
|
31
|
+
]) @reference.call
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
; Methods
|
|
2
|
-
(method
|
|
3
|
-
name: (identifier) @name.definition.method) @definition.method
|
|
4
|
-
|
|
5
|
-
; Singleton methods
|
|
6
|
-
(singleton_method
|
|
7
|
-
name: (identifier) @name.definition.method) @definition.method
|
|
8
|
-
|
|
9
|
-
; Classes
|
|
10
|
-
(class
|
|
11
|
-
name: (constant) @name.definition.class) @definition.class
|
|
12
|
-
|
|
13
|
-
; Modules
|
|
14
|
-
(module
|
|
15
|
-
name: (constant) @name.definition.module) @definition.module
|
|
16
|
-
|
|
17
|
-
; Call references
|
|
18
|
-
(call
|
|
19
|
-
method: (identifier) @name.reference.call) @reference.call
|
|
1
|
+
; Methods
|
|
2
|
+
(method
|
|
3
|
+
name: (identifier) @name.definition.method) @definition.method
|
|
4
|
+
|
|
5
|
+
; Singleton methods
|
|
6
|
+
(singleton_method
|
|
7
|
+
name: (identifier) @name.definition.method) @definition.method
|
|
8
|
+
|
|
9
|
+
; Classes
|
|
10
|
+
(class
|
|
11
|
+
name: (constant) @name.definition.class) @definition.class
|
|
12
|
+
|
|
13
|
+
; Modules
|
|
14
|
+
(module
|
|
15
|
+
name: (constant) @name.definition.module) @definition.module
|
|
16
|
+
|
|
17
|
+
; Call references
|
|
18
|
+
(call
|
|
19
|
+
method: (identifier) @name.reference.call) @reference.call
|