feed-the-machine 1.0.0 → 1.2.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/bin/generate-manifest.mjs +253 -0
- package/bin/install.mjs +134 -4
- package/docs/HOOKS.md +243 -0
- package/docs/INBOX.md +233 -0
- package/ftm/SKILL.md +34 -0
- package/ftm-audit/SKILL.md +69 -0
- package/ftm-brainstorm/SKILL.md +51 -0
- package/ftm-browse/SKILL.md +39 -0
- package/ftm-capture/SKILL.md +370 -0
- package/ftm-capture.yml +4 -0
- package/ftm-codex-gate/SKILL.md +59 -0
- package/ftm-config/SKILL.md +35 -0
- package/ftm-council/SKILL.md +56 -0
- package/ftm-dashboard/SKILL.md +163 -0
- package/ftm-debug/SKILL.md +84 -0
- package/ftm-diagram/SKILL.md +44 -0
- package/ftm-executor/SKILL.md +97 -0
- package/ftm-git/SKILL.md +60 -0
- package/ftm-inbox/backend/__init__.py +0 -0
- package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/adapters/__init__.py +0 -0
- package/ftm-inbox/backend/adapters/_retry.py +64 -0
- package/ftm-inbox/backend/adapters/base.py +230 -0
- package/ftm-inbox/backend/adapters/freshservice.py +104 -0
- package/ftm-inbox/backend/adapters/gmail.py +125 -0
- package/ftm-inbox/backend/adapters/jira.py +136 -0
- package/ftm-inbox/backend/adapters/registry.py +192 -0
- package/ftm-inbox/backend/adapters/slack.py +110 -0
- package/ftm-inbox/backend/db/__init__.py +0 -0
- package/ftm-inbox/backend/db/connection.py +54 -0
- package/ftm-inbox/backend/db/schema.py +78 -0
- package/ftm-inbox/backend/executor/__init__.py +7 -0
- package/ftm-inbox/backend/executor/engine.py +149 -0
- package/ftm-inbox/backend/executor/step_runner.py +98 -0
- package/ftm-inbox/backend/main.py +103 -0
- package/ftm-inbox/backend/models/__init__.py +1 -0
- package/ftm-inbox/backend/models/unified_task.py +36 -0
- package/ftm-inbox/backend/planner/__init__.py +6 -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/planner/generator.py +127 -0
- package/ftm-inbox/backend/planner/schema.py +34 -0
- package/ftm-inbox/backend/requirements.txt +5 -0
- package/ftm-inbox/backend/routes/__init__.py +0 -0
- package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/routes/execute.py +186 -0
- package/ftm-inbox/backend/routes/health.py +52 -0
- package/ftm-inbox/backend/routes/inbox.py +68 -0
- package/ftm-inbox/backend/routes/plan.py +271 -0
- package/ftm-inbox/bin/launchagent.mjs +91 -0
- package/ftm-inbox/bin/setup.mjs +188 -0
- package/ftm-inbox/bin/start.sh +10 -0
- package/ftm-inbox/bin/status.sh +17 -0
- package/ftm-inbox/bin/stop.sh +8 -0
- package/ftm-inbox/config.example.yml +55 -0
- package/ftm-inbox/package-lock.json +2898 -0
- package/ftm-inbox/package.json +26 -0
- package/ftm-inbox/postcss.config.js +6 -0
- package/ftm-inbox/src/app.css +199 -0
- package/ftm-inbox/src/app.html +18 -0
- package/ftm-inbox/src/lib/api.ts +166 -0
- package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -0
- package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -0
- package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -0
- package/ftm-inbox/src/lib/components/PlanView.svelte +206 -0
- package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -0
- package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -0
- package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -0
- package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -0
- package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -0
- package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -0
- package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -0
- package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -0
- package/ftm-inbox/src/lib/theme.ts +47 -0
- package/ftm-inbox/src/routes/+layout.svelte +76 -0
- package/ftm-inbox/src/routes/+page.svelte +401 -0
- package/ftm-inbox/static/favicon.png +0 -0
- package/ftm-inbox/svelte.config.js +12 -0
- package/ftm-inbox/tailwind.config.ts +63 -0
- package/ftm-inbox/tsconfig.json +13 -0
- package/ftm-inbox/vite.config.ts +6 -0
- package/ftm-intent/SKILL.md +44 -0
- package/ftm-manifest.json +3794 -0
- package/ftm-map/SKILL.md +259 -0
- package/ftm-map/scripts/db.py +391 -0
- package/ftm-map/scripts/index.py +341 -0
- package/ftm-map/scripts/parser.py +455 -0
- package/ftm-map/scripts/queries/.gitkeep +0 -0
- package/ftm-map/scripts/queries/javascript-tags.scm +23 -0
- package/ftm-map/scripts/queries/python-tags.scm +17 -0
- package/ftm-map/scripts/queries/typescript-tags.scm +29 -0
- package/ftm-map/scripts/query.py +149 -0
- package/ftm-map/scripts/requirements.txt +2 -0
- package/ftm-map/scripts/setup-hooks.sh +27 -0
- package/ftm-map/scripts/setup.sh +45 -0
- package/ftm-map/scripts/test_db.py +124 -0
- package/ftm-map/scripts/test_parser.py +106 -0
- package/ftm-map/scripts/test_query.py +66 -0
- package/ftm-map/scripts/tests/fixtures/__init__.py +0 -0
- package/ftm-map/scripts/tests/fixtures/sample_project/api.ts +16 -0
- package/ftm-map/scripts/tests/fixtures/sample_project/auth.py +15 -0
- package/ftm-map/scripts/tests/fixtures/sample_project/utils.js +16 -0
- package/ftm-map/scripts/views.py +545 -0
- package/ftm-mind/SKILL.md +173 -66
- package/ftm-pause/SKILL.md +43 -0
- package/ftm-researcher/SKILL.md +275 -0
- package/ftm-researcher/evals/agent-diversity.yaml +17 -0
- package/ftm-researcher/evals/synthesis-quality.yaml +12 -0
- package/ftm-researcher/evals/trigger-accuracy.yaml +39 -0
- package/ftm-researcher/references/adaptive-search.md +116 -0
- package/ftm-researcher/references/agent-prompts.md +193 -0
- package/ftm-researcher/references/council-integration.md +193 -0
- package/ftm-researcher/references/output-format.md +203 -0
- package/ftm-researcher/references/synthesis-pipeline.md +165 -0
- package/ftm-researcher/scripts/score_credibility.py +234 -0
- package/ftm-researcher/scripts/validate_research.py +92 -0
- package/ftm-resume/SKILL.md +47 -0
- package/ftm-retro/SKILL.md +54 -0
- package/ftm-routine/SKILL.md +170 -0
- package/ftm-state/blackboard/capabilities.json +5 -0
- package/ftm-state/blackboard/capabilities.schema.json +27 -0
- package/ftm-upgrade/SKILL.md +41 -0
- package/ftm-upgrade/scripts/check-version.sh +1 -1
- package/ftm-upgrade/scripts/upgrade.sh +1 -1
- package/hooks/ftm-blackboard-enforcer.sh +94 -0
- package/hooks/ftm-discovery-reminder.sh +90 -0
- package/hooks/ftm-drafts-gate.sh +61 -0
- package/hooks/ftm-event-logger.mjs +107 -0
- package/hooks/ftm-map-autodetect.sh +79 -0
- package/hooks/ftm-pending-sync-check.sh +22 -0
- package/hooks/ftm-plan-gate.sh +96 -0
- package/hooks/ftm-post-commit-trigger.sh +57 -0
- package/hooks/settings-template.json +81 -0
- package/install.sh +140 -11
- package/package.json +12 -2
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""ftm-map indexer: builds the code knowledge graph from source files."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# Add scripts dir to path for sibling imports
|
|
14
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
15
|
+
|
|
16
|
+
from db import (
|
|
17
|
+
get_connection,
|
|
18
|
+
add_symbol,
|
|
19
|
+
remove_symbols_by_file,
|
|
20
|
+
add_edge,
|
|
21
|
+
get_symbol_by_name,
|
|
22
|
+
get_stats,
|
|
23
|
+
)
|
|
24
|
+
from parser import parse_file, extract_relationships, EXTENSION_MAP
|
|
25
|
+
|
|
26
|
+
META_REGISTRY = os.path.expanduser("~/.claude/ftm-state/maps/index.json")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# File discovery
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def discover_files(project_root: str) -> list[str]:
|
|
35
|
+
"""Get tracked source files using git ls-files.
|
|
36
|
+
|
|
37
|
+
Filters to files whose extensions are in EXTENSION_MAP so only
|
|
38
|
+
tree-sitter-parseable files are returned. Returns absolute paths.
|
|
39
|
+
"""
|
|
40
|
+
result = subprocess.run(
|
|
41
|
+
["git", "ls-files"],
|
|
42
|
+
capture_output=True,
|
|
43
|
+
text=True,
|
|
44
|
+
cwd=project_root,
|
|
45
|
+
)
|
|
46
|
+
if result.returncode != 0:
|
|
47
|
+
print(
|
|
48
|
+
f"Error: git ls-files failed: {result.stderr.strip()}",
|
|
49
|
+
file=sys.stderr,
|
|
50
|
+
)
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
supported_exts = set(EXTENSION_MAP.keys())
|
|
54
|
+
files = []
|
|
55
|
+
for line in result.stdout.strip().split("\n"):
|
|
56
|
+
if not line:
|
|
57
|
+
continue
|
|
58
|
+
ext = Path(line).suffix.lower()
|
|
59
|
+
if ext in supported_exts:
|
|
60
|
+
files.append(os.path.join(project_root, line))
|
|
61
|
+
return files
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Core indexing logic
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def index_files(conn, files: list[str], project_root: str) -> dict:
|
|
70
|
+
"""Parse and insert symbols + edges for a list of absolute file paths.
|
|
71
|
+
|
|
72
|
+
Two-phase approach:
|
|
73
|
+
Phase 1 — parse every file and insert all symbols so that the
|
|
74
|
+
symbol table is fully populated before edge resolution.
|
|
75
|
+
Phase 2 — extract relationships and resolve source/target names to
|
|
76
|
+
existing symbol IDs. Unknown targets are silently skipped.
|
|
77
|
+
|
|
78
|
+
Returns a dict with 'symbols' and 'edges' counts.
|
|
79
|
+
"""
|
|
80
|
+
total_symbols = 0
|
|
81
|
+
total_edges = 0
|
|
82
|
+
|
|
83
|
+
# Phase 1: insert all symbols first so cross-file edges can be resolved.
|
|
84
|
+
for fpath in files:
|
|
85
|
+
if not os.path.exists(fpath):
|
|
86
|
+
print(f"Warning: file not found, skipping: {fpath}", file=sys.stderr)
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
rel_path = os.path.relpath(fpath, project_root)
|
|
90
|
+
symbols = parse_file(fpath)
|
|
91
|
+
|
|
92
|
+
for sym in symbols:
|
|
93
|
+
add_symbol(
|
|
94
|
+
conn,
|
|
95
|
+
name=sym.name,
|
|
96
|
+
kind=sym.kind,
|
|
97
|
+
file_path=rel_path,
|
|
98
|
+
start_line=sym.start_line,
|
|
99
|
+
end_line=sym.end_line,
|
|
100
|
+
signature=sym.signature,
|
|
101
|
+
doc_comment=sym.doc_comment,
|
|
102
|
+
content_hash=sym.content_hash,
|
|
103
|
+
)
|
|
104
|
+
total_symbols += 1
|
|
105
|
+
|
|
106
|
+
# Phase 2: resolve and insert edges.
|
|
107
|
+
for fpath in files:
|
|
108
|
+
if not os.path.exists(fpath):
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
rels = extract_relationships(fpath)
|
|
112
|
+
for rel in rels:
|
|
113
|
+
source_rows = get_symbol_by_name(conn, rel.source_name)
|
|
114
|
+
target_rows = get_symbol_by_name(conn, rel.target_name)
|
|
115
|
+
|
|
116
|
+
# Skip if either end of the relationship is unresolvable.
|
|
117
|
+
if not source_rows or not target_rows:
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
add_edge(conn, source_rows[0]["id"], target_rows[0]["id"], rel.kind)
|
|
121
|
+
total_edges += 1
|
|
122
|
+
|
|
123
|
+
return {"symbols": total_symbols, "edges": total_edges}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Bootstrap mode
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def bootstrap(project_root: str) -> None:
|
|
132
|
+
"""Full scan: (re)build the entire code graph for *project_root*."""
|
|
133
|
+
abs_root = os.path.abspath(project_root)
|
|
134
|
+
start = time.time()
|
|
135
|
+
|
|
136
|
+
files = discover_files(abs_root)
|
|
137
|
+
if not files:
|
|
138
|
+
print(
|
|
139
|
+
json.dumps({"error": "No parseable source files found in git repository"}),
|
|
140
|
+
file=sys.stderr,
|
|
141
|
+
)
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
conn = get_connection(abs_root)
|
|
145
|
+
try:
|
|
146
|
+
# Full rebuild — clear existing content first.
|
|
147
|
+
# FTS5 rows must be removed before symbol rows because the content=
|
|
148
|
+
# table does not cascade deletes.
|
|
149
|
+
symbol_ids = [
|
|
150
|
+
row[0] for row in conn.execute("SELECT id FROM symbols").fetchall()
|
|
151
|
+
]
|
|
152
|
+
for sid in symbol_ids:
|
|
153
|
+
conn.execute("DELETE FROM symbols_fts WHERE rowid=?", (sid,))
|
|
154
|
+
conn.execute("DELETE FROM symbols")
|
|
155
|
+
conn.execute("DELETE FROM edges")
|
|
156
|
+
|
|
157
|
+
stats = index_files(conn, files, abs_root)
|
|
158
|
+
conn.commit()
|
|
159
|
+
|
|
160
|
+
duration = time.time() - start
|
|
161
|
+
result = {
|
|
162
|
+
"mode": "bootstrap",
|
|
163
|
+
"files_parsed": len(files),
|
|
164
|
+
"symbols": stats["symbols"],
|
|
165
|
+
"edges": stats["edges"],
|
|
166
|
+
"duration_s": round(duration, 2),
|
|
167
|
+
}
|
|
168
|
+
print(json.dumps(result))
|
|
169
|
+
update_meta_registry(abs_root, stats["symbols"])
|
|
170
|
+
except Exception as exc: # noqa: BLE001
|
|
171
|
+
print(f"Error during bootstrap: {exc}", file=sys.stderr)
|
|
172
|
+
conn.rollback()
|
|
173
|
+
conn.close()
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
finally:
|
|
176
|
+
conn.close()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# Incremental mode
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def incremental(project_root: str, files_str: str) -> None:
|
|
185
|
+
"""Incremental update: re-index only the specified files.
|
|
186
|
+
|
|
187
|
+
*files_str* is a comma-separated list of file paths (relative or absolute).
|
|
188
|
+
Old symbol/edge data for each file is removed before re-parsing so stale
|
|
189
|
+
entries do not accumulate.
|
|
190
|
+
"""
|
|
191
|
+
abs_root = os.path.abspath(project_root)
|
|
192
|
+
start = time.time()
|
|
193
|
+
|
|
194
|
+
raw_files = [f.strip() for f in files_str.split(",") if f.strip()]
|
|
195
|
+
abs_files = [
|
|
196
|
+
f if os.path.isabs(f) else os.path.join(abs_root, f) for f in raw_files
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
conn = get_connection(abs_root)
|
|
200
|
+
try:
|
|
201
|
+
# Remove stale data for all targeted files before re-parsing.
|
|
202
|
+
for fpath in abs_files:
|
|
203
|
+
rel_path = os.path.relpath(fpath, abs_root)
|
|
204
|
+
remove_symbols_by_file(conn, rel_path)
|
|
205
|
+
|
|
206
|
+
existing_files = [f for f in abs_files if os.path.exists(f)]
|
|
207
|
+
if not existing_files:
|
|
208
|
+
print(
|
|
209
|
+
json.dumps({"error": "None of the specified files exist"}),
|
|
210
|
+
file=sys.stderr,
|
|
211
|
+
)
|
|
212
|
+
conn.close()
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
stats = index_files(conn, existing_files, abs_root)
|
|
216
|
+
conn.commit()
|
|
217
|
+
|
|
218
|
+
db_stats = get_stats(conn)
|
|
219
|
+
duration = time.time() - start
|
|
220
|
+
result = {
|
|
221
|
+
"mode": "incremental",
|
|
222
|
+
"files_parsed": len(existing_files),
|
|
223
|
+
"symbols": stats["symbols"],
|
|
224
|
+
"edges": stats["edges"],
|
|
225
|
+
"duration_s": round(duration, 2),
|
|
226
|
+
}
|
|
227
|
+
print(json.dumps(result))
|
|
228
|
+
update_meta_registry(abs_root, db_stats["symbols"])
|
|
229
|
+
except Exception as exc: # noqa: BLE001
|
|
230
|
+
print(f"Error during incremental update: {exc}", file=sys.stderr)
|
|
231
|
+
conn.rollback()
|
|
232
|
+
conn.close()
|
|
233
|
+
sys.exit(1)
|
|
234
|
+
finally:
|
|
235
|
+
conn.close()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
# Meta-registry management
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def update_meta_registry(project_root: str, symbol_count: int) -> None:
|
|
244
|
+
"""Upsert project entry in the global meta-registry at META_REGISTRY."""
|
|
245
|
+
registry_dir = os.path.dirname(META_REGISTRY)
|
|
246
|
+
os.makedirs(registry_dir, exist_ok=True)
|
|
247
|
+
|
|
248
|
+
registry: dict = {"projects": []}
|
|
249
|
+
if os.path.exists(META_REGISTRY):
|
|
250
|
+
try:
|
|
251
|
+
with open(META_REGISTRY) as fh:
|
|
252
|
+
registry = json.load(fh)
|
|
253
|
+
except (json.JSONDecodeError, IOError):
|
|
254
|
+
# Corrupt or unreadable registry — start fresh.
|
|
255
|
+
registry = {"projects": []}
|
|
256
|
+
|
|
257
|
+
abs_root = os.path.abspath(project_root)
|
|
258
|
+
db_path = os.path.join(abs_root, ".ftm-map", "map.db")
|
|
259
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
260
|
+
|
|
261
|
+
found = False
|
|
262
|
+
for proj in registry["projects"]:
|
|
263
|
+
if proj.get("path") == abs_root:
|
|
264
|
+
proj["last_indexed"] = now
|
|
265
|
+
proj["symbol_count"] = symbol_count
|
|
266
|
+
found = True
|
|
267
|
+
break
|
|
268
|
+
|
|
269
|
+
if not found:
|
|
270
|
+
registry["projects"].append(
|
|
271
|
+
{
|
|
272
|
+
"path": abs_root,
|
|
273
|
+
"db_path": db_path,
|
|
274
|
+
"last_indexed": now,
|
|
275
|
+
"symbol_count": symbol_count,
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
with open(META_REGISTRY, "w") as fh:
|
|
280
|
+
json.dump(registry, fh, indent=2)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
# CLI entry point
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def main() -> None:
|
|
289
|
+
parser = argparse.ArgumentParser(
|
|
290
|
+
description="ftm-map indexer — builds the code knowledge graph from source files.",
|
|
291
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
292
|
+
epilog=(
|
|
293
|
+
"Examples:\n"
|
|
294
|
+
" python3 index.py --bootstrap /path/to/project\n"
|
|
295
|
+
" python3 index.py --incremental --files src/foo.ts,src/bar.py\n"
|
|
296
|
+
" python3 index.py --incremental --files src/foo.ts --project-root /path/to/project\n"
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
mode = parser.add_mutually_exclusive_group(required=True)
|
|
301
|
+
mode.add_argument(
|
|
302
|
+
"--bootstrap",
|
|
303
|
+
metavar="PROJECT_ROOT",
|
|
304
|
+
help="Full scan: index all tracked source files in PROJECT_ROOT.",
|
|
305
|
+
)
|
|
306
|
+
mode.add_argument(
|
|
307
|
+
"--incremental",
|
|
308
|
+
action="store_true",
|
|
309
|
+
help="Incremental update: re-index only the files given by --files.",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
parser.add_argument(
|
|
313
|
+
"--files",
|
|
314
|
+
metavar="FILE_LIST",
|
|
315
|
+
help="Comma-separated list of files to re-index (required for --incremental).",
|
|
316
|
+
)
|
|
317
|
+
parser.add_argument(
|
|
318
|
+
"--project-root",
|
|
319
|
+
metavar="PATH",
|
|
320
|
+
default=None,
|
|
321
|
+
help=(
|
|
322
|
+
"Project root used to locate the database for incremental mode. "
|
|
323
|
+
"Defaults to the current working directory."
|
|
324
|
+
),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
args = parser.parse_args()
|
|
328
|
+
|
|
329
|
+
if args.bootstrap:
|
|
330
|
+
bootstrap(args.bootstrap)
|
|
331
|
+
else:
|
|
332
|
+
# Incremental mode
|
|
333
|
+
if not args.files:
|
|
334
|
+
print("Error: --incremental requires --files", file=sys.stderr)
|
|
335
|
+
sys.exit(1)
|
|
336
|
+
project_root = args.project_root or os.getcwd()
|
|
337
|
+
incremental(project_root, args.files)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
if __name__ == "__main__":
|
|
341
|
+
main()
|