honestcode 0.5.0__py3-none-any.whl
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.
- honestcode/__init__.py +19 -0
- honestcode/cli.py +428 -0
- honestcode/graph/__init__.py +13 -0
- honestcode/graph/graph_store.py +276 -0
- honestcode/graph/watcher.py +116 -0
- honestcode/guards/__init__.py +104 -0
- honestcode/guards/guard.py +777 -0
- honestcode/guards/store.py +457 -0
- honestcode/guards/taxonomy.py +228 -0
- honestcode/honest/__init__.py +19 -0
- honestcode/honest/project_binding.py +200 -0
- honestcode/honest/router.py +205 -0
- honestcode/honest/symbol_index.py +446 -0
- honestcode/integrations/__init__.py +14 -0
- honestcode/integrations/pydantic_ai.py +304 -0
- honestcode/mcp/__init__.py +46 -0
- honestcode/mcp/knowledge_base.py +290 -0
- honestcode/mcp/server.py +357 -0
- honestcode/mcp/tools.py +2002 -0
- honestcode/py.typed +0 -0
- honestcode/sandbox/__init__.py +334 -0
- honestcode/structure/__init__.py +6 -0
- honestcode/structure/extractor.py +353 -0
- honestcode/structure/multi_lang.py +267 -0
- honestcode/structure/relations.py +47 -0
- honestcode/structure/utils.py +262 -0
- honestcode/verify/__init__.py +19 -0
- honestcode/verify/evidence.py +195 -0
- honestcode/verify/surface.py +497 -0
- honestcode/verify/verify.py +1074 -0
- honestcode-0.5.0.dist-info/METADATA +669 -0
- honestcode-0.5.0.dist-info/RECORD +36 -0
- honestcode-0.5.0.dist-info/WHEEL +5 -0
- honestcode-0.5.0.dist-info/entry_points.txt +3 -0
- honestcode-0.5.0.dist-info/licenses/LICENSE +21 -0
- honestcode-0.5.0.dist-info/top_level.txt +1 -0
honestcode/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""HonestCode: verify AI-generated code against project structure and installed APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from honestcode import honest, mcp, sandbox, structure
|
|
9
|
+
|
|
10
|
+
__version__ = "0.5.0"
|
|
11
|
+
__all__ = ["honest", "mcp", "sandbox", "structure"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __getattr__(name: str):
|
|
15
|
+
if name in __all__:
|
|
16
|
+
import importlib
|
|
17
|
+
|
|
18
|
+
return importlib.import_module(f"honestcode.{name}")
|
|
19
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
honestcode/cli.py
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for HonestCode.
|
|
3
|
+
|
|
4
|
+
Every MCP tool has a CLI equivalent so the same capabilities can be used
|
|
5
|
+
from scripts, CI pipelines, and non-MCP harnesses without launching a
|
|
6
|
+
server process.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
honestcode index /path/to/project
|
|
10
|
+
honestcode deps /path/to/project
|
|
11
|
+
honestcode scan src/main.py
|
|
12
|
+
honestcode check-symbol pkg.core.helper
|
|
13
|
+
honestcode check-api pandas.read_csv
|
|
14
|
+
honestcode validate "for x in None: pass"
|
|
15
|
+
honestcode dead-code --entrypoints pkg.cli.main
|
|
16
|
+
honestcode search "def \\w+_helper"
|
|
17
|
+
honestcode call-graph pkg.core.helper
|
|
18
|
+
honestcode similar --threshold 0.85
|
|
19
|
+
honestcode stats
|
|
20
|
+
honestcode choose-tool "is my_symbol defined"
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import sys
|
|
28
|
+
import time
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import TYPE_CHECKING, Any
|
|
31
|
+
|
|
32
|
+
from honestcode.honest import project_binding as _binding
|
|
33
|
+
from honestcode.mcp import tools as _tools
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from collections.abc import Callable
|
|
37
|
+
|
|
38
|
+
__all__ = ["main"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _emit(result: dict[str, Any]) -> int:
|
|
42
|
+
"""Print *result* as JSON to stdout and return an exit code."""
|
|
43
|
+
sys.stdout.write(json.dumps(result, indent=2, default=str) + "\n")
|
|
44
|
+
# Non-zero exit when the operation reported a failure.
|
|
45
|
+
return 0 if result.get("success", True) else 1
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _emit_issues(result: dict[str, Any], issue_key: str = "issues") -> int:
|
|
49
|
+
"""Like _emit but exit non-zero when issues were found."""
|
|
50
|
+
sys.stdout.write(json.dumps(result, indent=2, default=str) + "\n")
|
|
51
|
+
if not result.get("success", True):
|
|
52
|
+
return 1
|
|
53
|
+
n = len(result.get(issue_key, []))
|
|
54
|
+
return 1 if n else 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _ensure_index(args: argparse.Namespace) -> None:
|
|
58
|
+
"""Load the project index for a fresh CLI process.
|
|
59
|
+
|
|
60
|
+
Every command runs in its own process, so an index built by an earlier
|
|
61
|
+
``honestcode index`` call is not in memory. Resolve the project from
|
|
62
|
+
``--root`` (nearest ``.git`` / ``.honestcode``) and build/reuse the cached
|
|
63
|
+
index before any query that needs it.
|
|
64
|
+
"""
|
|
65
|
+
explicit = getattr(args, "root", None)
|
|
66
|
+
if explicit is None and _tools._project_root is not None:
|
|
67
|
+
# Same-process usage (tests, embedded callers): the caller already
|
|
68
|
+
# chose a project; revalidate it instead of silently switching to cwd.
|
|
69
|
+
_tools.index_project(str(_tools._project_root))
|
|
70
|
+
return
|
|
71
|
+
if explicit is not None:
|
|
72
|
+
# An explicit --root is authoritative: do not walk up to an enclosing
|
|
73
|
+
# git repository and index projects the caller did not name.
|
|
74
|
+
start = Path(explicit)
|
|
75
|
+
if start.is_file():
|
|
76
|
+
start = start.parent
|
|
77
|
+
root = start
|
|
78
|
+
else:
|
|
79
|
+
start = Path.cwd()
|
|
80
|
+
root = _binding.find_project_root(str(start)) or start
|
|
81
|
+
_tools.index_project(str(root))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _cmd_index(args: argparse.Namespace) -> int:
|
|
85
|
+
return _emit(_tools.index_project(args.path, force_rebuild=args.force, watch=args.watch))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _cmd_affected(args: argparse.Namespace) -> int:
|
|
89
|
+
_ensure_index(args)
|
|
90
|
+
return _emit(_tools.affected_files(base=args.base, head=args.head, max_depth=args.depth))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _cmd_impact(args: argparse.Namespace) -> int:
|
|
94
|
+
_ensure_index(args)
|
|
95
|
+
return _emit(_tools.explore_impact(args.symbol, max_depth=args.depth))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _cmd_watch(args: argparse.Namespace) -> int:
|
|
99
|
+
"""Index and keep the project fresh until interrupted (Ctrl+C)."""
|
|
100
|
+
res = _tools.index_project(args.path, force_rebuild=args.force, watch=True)
|
|
101
|
+
_emit(res)
|
|
102
|
+
if not res.get("success"):
|
|
103
|
+
return 1
|
|
104
|
+
print(f"Watching {args.path} — press Ctrl+C to stop.", file=sys.stderr)
|
|
105
|
+
try:
|
|
106
|
+
while True:
|
|
107
|
+
time.sleep(60)
|
|
108
|
+
except KeyboardInterrupt:
|
|
109
|
+
_emit(_tools.stop_watching())
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _cmd_init(args: argparse.Namespace) -> int:
|
|
114
|
+
return _emit(_binding.init_project(args.path, force=args.force))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _cmd_uninit(args: argparse.Namespace) -> int:
|
|
118
|
+
return _emit(_binding.uninit_project(args.path))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _cmd_install(args: argparse.Namespace) -> int:
|
|
122
|
+
return _emit(
|
|
123
|
+
_binding.install_mcp_config(
|
|
124
|
+
path=args.path,
|
|
125
|
+
client=args.client,
|
|
126
|
+
dry_run=args.dry_run,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _cmd_root(args: argparse.Namespace) -> int:
|
|
132
|
+
root = _binding.find_project_root(args.path)
|
|
133
|
+
if root is None:
|
|
134
|
+
return _emit(
|
|
135
|
+
{"success": False, "error": "No project root found (no .honestcode/ or .git/)"}
|
|
136
|
+
)
|
|
137
|
+
return _emit({"success": True, "root": str(root)})
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _cmd_deps(args: argparse.Namespace) -> int:
|
|
141
|
+
return _emit(_tools.load_project_deps(args.path))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _cmd_scan(args: argparse.Namespace) -> int:
|
|
145
|
+
report = _tools.scan_file(args.file)
|
|
146
|
+
if not report.get("success", True):
|
|
147
|
+
return _emit(report)
|
|
148
|
+
if getattr(args, "format", "json") == "text":
|
|
149
|
+
from honestcode.verify.evidence import render_text
|
|
150
|
+
|
|
151
|
+
sys.stdout.write(render_text(report) + "\n")
|
|
152
|
+
return 1 if report["status"] == "fail" else 0
|
|
153
|
+
return _emit_issues(report, issue_key="issues")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _cmd_verify(args: argparse.Namespace) -> int:
|
|
157
|
+
report = _tools.verify_file(args.file)
|
|
158
|
+
if not report.get("success", True):
|
|
159
|
+
return _emit(report)
|
|
160
|
+
sys.stdout.write(report["text"] + "\n")
|
|
161
|
+
return 1 if report["status"] == "fail" else 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _cmd_check_symbol(args: argparse.Namespace) -> int:
|
|
165
|
+
_ensure_index(args)
|
|
166
|
+
res = _tools.check_symbol(args.symbol, file_path=args.file or None)
|
|
167
|
+
_emit(res)
|
|
168
|
+
return 0 if res.get("defined") else 1
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _cmd_check_api(args: argparse.Namespace) -> int:
|
|
172
|
+
res = _tools.check_api(args.api)
|
|
173
|
+
_emit(res)
|
|
174
|
+
return 0 if res.get("valid") else 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _cmd_validate(args: argparse.Namespace) -> int:
|
|
178
|
+
return _emit_issues(_tools.validate_types(args.code), issue_key="issues")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _cmd_execute(args: argparse.Namespace) -> int:
|
|
182
|
+
return _emit(_tools.execute_code(args.code, prelude=args.prelude, known_names=args.known_names))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _cmd_dead_code(args: argparse.Namespace) -> int:
|
|
186
|
+
_ensure_index(args)
|
|
187
|
+
return _emit_issues(
|
|
188
|
+
_tools.find_dead_code(
|
|
189
|
+
entrypoints=args.entrypoints,
|
|
190
|
+
ignore_patterns=args.ignore,
|
|
191
|
+
include_tests=args.include_tests,
|
|
192
|
+
),
|
|
193
|
+
issue_key="dead_symbols",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _cmd_similar(args: argparse.Namespace) -> int:
|
|
198
|
+
_ensure_index(args)
|
|
199
|
+
return _emit_issues(_tools.find_similar_code(threshold=args.threshold), issue_key="pairs")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _cmd_call_graph(args: argparse.Namespace) -> int:
|
|
203
|
+
_ensure_index(args)
|
|
204
|
+
return _emit(_tools.explore_call_graph(args.symbol))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _cmd_search(args: argparse.Namespace) -> int:
|
|
208
|
+
_ensure_index(args)
|
|
209
|
+
return _emit_issues(_tools.search_code(args.pattern, glob=args.glob), issue_key="matches")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _cmd_load_package(args: argparse.Namespace) -> int:
|
|
213
|
+
return _emit(_tools.load_package_apis(args.package))
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _cmd_stats(args: argparse.Namespace) -> int:
|
|
217
|
+
_ensure_index(args)
|
|
218
|
+
return _emit(_tools.get_project_stats())
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _cmd_choose_tool(args: argparse.Namespace) -> int:
|
|
222
|
+
return _emit(_tools.choose_tool(args.query))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _version_string() -> str:
|
|
226
|
+
try:
|
|
227
|
+
from importlib.metadata import version
|
|
228
|
+
|
|
229
|
+
return f"honestcode {version('honestcode')}"
|
|
230
|
+
except Exception: # noqa: BLE001 - package metadata unavailable (e.g. source tree)
|
|
231
|
+
from honestcode import __version__
|
|
232
|
+
|
|
233
|
+
return f"honestcode {__version__}"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _add_root_arg(parser: argparse.ArgumentParser) -> None:
|
|
237
|
+
parser.add_argument(
|
|
238
|
+
"--root",
|
|
239
|
+
default=None,
|
|
240
|
+
help="Project root (default: cwd, or the nearest .git/.honestcode).",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
245
|
+
parser = argparse.ArgumentParser(
|
|
246
|
+
prog="honestcode",
|
|
247
|
+
description="HonestCode CLI — verify AI-generated code against your project.",
|
|
248
|
+
)
|
|
249
|
+
parser.add_argument("--version", action="version", version=_version_string())
|
|
250
|
+
sub = parser.add_subparsers(dest="command", required=True, metavar="COMMAND")
|
|
251
|
+
|
|
252
|
+
p = sub.add_parser("index", help="Build (or reuse) the project symbol index.")
|
|
253
|
+
p.add_argument("path", help="Project root directory.")
|
|
254
|
+
p.add_argument("--force", action="store_true", help="Force rebuild the index.")
|
|
255
|
+
p.add_argument(
|
|
256
|
+
"--watch",
|
|
257
|
+
action="store_true",
|
|
258
|
+
help="Start a background watcher that re-indexes on file changes.",
|
|
259
|
+
)
|
|
260
|
+
p.set_defaults(func=_cmd_index)
|
|
261
|
+
|
|
262
|
+
p = sub.add_parser(
|
|
263
|
+
"watch",
|
|
264
|
+
help="Index a project and keep it fresh (background watcher, Ctrl+C to stop).",
|
|
265
|
+
)
|
|
266
|
+
p.add_argument("path", help="Project root directory.")
|
|
267
|
+
p.add_argument("--force", action="store_true", help="Force rebuild the index.")
|
|
268
|
+
p.set_defaults(func=_cmd_watch)
|
|
269
|
+
|
|
270
|
+
p = sub.add_parser("deps", help="Load dependency APIs from requirements.txt / pyproject.toml.")
|
|
271
|
+
p.add_argument("path", help="Project root directory.")
|
|
272
|
+
p.set_defaults(func=_cmd_deps)
|
|
273
|
+
|
|
274
|
+
p = sub.add_parser("scan", help="Scan a file for undefined calls and API issues.")
|
|
275
|
+
p.add_argument("file", help="Path to the Python file to scan.")
|
|
276
|
+
p.add_argument(
|
|
277
|
+
"--format",
|
|
278
|
+
choices=["json", "text"],
|
|
279
|
+
default="json",
|
|
280
|
+
help="Output format (default: json).",
|
|
281
|
+
)
|
|
282
|
+
p.set_defaults(func=_cmd_scan)
|
|
283
|
+
|
|
284
|
+
p = sub.add_parser("verify", help="Verify a file and print agent-readable evidence.")
|
|
285
|
+
p.add_argument("file", help="Path to the Python file to verify.")
|
|
286
|
+
p.set_defaults(func=_cmd_verify)
|
|
287
|
+
|
|
288
|
+
p = sub.add_parser("check-symbol", help="Check whether a symbol is defined in the project.")
|
|
289
|
+
p.add_argument("symbol", help="Module-qualified symbol name, e.g. pkg.core.helper.")
|
|
290
|
+
p.add_argument("--file", default="", help="Require the symbol to be defined in this file.")
|
|
291
|
+
_add_root_arg(p)
|
|
292
|
+
p.set_defaults(func=_cmd_check_symbol)
|
|
293
|
+
|
|
294
|
+
p = sub.add_parser("check-api", help="Verify a library API call exists.")
|
|
295
|
+
p.add_argument("api", help="Fully qualified API name, e.g. pandas.read_csv.")
|
|
296
|
+
p.set_defaults(func=_cmd_check_api)
|
|
297
|
+
|
|
298
|
+
p = sub.add_parser("validate", help="Run structural type checks on a code snippet.")
|
|
299
|
+
p.add_argument("code", help="Python source to validate.")
|
|
300
|
+
p.set_defaults(func=_cmd_validate)
|
|
301
|
+
|
|
302
|
+
p = sub.add_parser("execute", help="Execute code in a sandboxed subprocess.")
|
|
303
|
+
p.add_argument("code", help="Python source to execute.")
|
|
304
|
+
p.add_argument("--prelude", default="", help="Setup code run before the main code.")
|
|
305
|
+
p.add_argument(
|
|
306
|
+
"--known-names",
|
|
307
|
+
nargs="*",
|
|
308
|
+
default=None,
|
|
309
|
+
help="Known symbol names for typo suggestions.",
|
|
310
|
+
)
|
|
311
|
+
p.set_defaults(func=_cmd_execute)
|
|
312
|
+
|
|
313
|
+
p = sub.add_parser(
|
|
314
|
+
"dead-code",
|
|
315
|
+
help="Find unused symbols. Exit code 1 if any dead symbols are found.",
|
|
316
|
+
)
|
|
317
|
+
p.add_argument(
|
|
318
|
+
"--entrypoints",
|
|
319
|
+
nargs="*",
|
|
320
|
+
default=None,
|
|
321
|
+
help="Symbol names to always treat as alive.",
|
|
322
|
+
)
|
|
323
|
+
p.add_argument(
|
|
324
|
+
"--ignore",
|
|
325
|
+
nargs="*",
|
|
326
|
+
default=None,
|
|
327
|
+
help="Regex patterns for files to exclude from dead-code results.",
|
|
328
|
+
)
|
|
329
|
+
p.add_argument(
|
|
330
|
+
"--no-tests",
|
|
331
|
+
dest="include_tests",
|
|
332
|
+
action="store_false",
|
|
333
|
+
help="Do not treat symbols in test files as alive.",
|
|
334
|
+
)
|
|
335
|
+
_add_root_arg(p)
|
|
336
|
+
p.set_defaults(func=_cmd_dead_code, include_tests=True)
|
|
337
|
+
|
|
338
|
+
p = sub.add_parser("similar", help="Find function-level code clones across the project.")
|
|
339
|
+
p.add_argument(
|
|
340
|
+
"--threshold",
|
|
341
|
+
type=float,
|
|
342
|
+
default=0.85,
|
|
343
|
+
help="Similarity ratio above which two functions are reported (0-1).",
|
|
344
|
+
)
|
|
345
|
+
_add_root_arg(p)
|
|
346
|
+
p.set_defaults(func=_cmd_similar)
|
|
347
|
+
|
|
348
|
+
p = sub.add_parser("call-graph", help="Explore callers and callees of a symbol.")
|
|
349
|
+
p.add_argument("symbol", help="Symbol name to explore.")
|
|
350
|
+
_add_root_arg(p)
|
|
351
|
+
p.set_defaults(func=_cmd_call_graph)
|
|
352
|
+
|
|
353
|
+
p = sub.add_parser("impact", help="Compute the blast radius of a symbol.")
|
|
354
|
+
p.add_argument("symbol", help="Symbol name to analyze.")
|
|
355
|
+
p.add_argument("--depth", type=int, default=3, help="Max call-graph hops (default: 3).")
|
|
356
|
+
_add_root_arg(p)
|
|
357
|
+
p.set_defaults(func=_cmd_impact)
|
|
358
|
+
|
|
359
|
+
p = sub.add_parser(
|
|
360
|
+
"affected",
|
|
361
|
+
help="Files/tests that may be affected by a git diff (from the call graph).",
|
|
362
|
+
)
|
|
363
|
+
p.add_argument("--base", default="HEAD", help="Git ref to diff against (default: HEAD).")
|
|
364
|
+
p.add_argument("--head", default=None, help="Optional second ref for base..head diffs.")
|
|
365
|
+
p.add_argument("--depth", type=int, default=4, help="Max call-graph hops (default: 4).")
|
|
366
|
+
_add_root_arg(p)
|
|
367
|
+
p.set_defaults(func=_cmd_affected)
|
|
368
|
+
|
|
369
|
+
p = sub.add_parser("search", help="Search project source code with a regex pattern.")
|
|
370
|
+
p.add_argument("pattern", help="Regular expression to search for.")
|
|
371
|
+
p.add_argument("--glob", default="*.py", help="Glob pattern for files to search.")
|
|
372
|
+
_add_root_arg(p)
|
|
373
|
+
p.set_defaults(func=_cmd_search)
|
|
374
|
+
|
|
375
|
+
p = sub.add_parser("load-package", help="Load API signatures for a specific package.")
|
|
376
|
+
p.add_argument("package", help="Importable package name, e.g. numpy.")
|
|
377
|
+
p.set_defaults(func=_cmd_load_package)
|
|
378
|
+
|
|
379
|
+
p = sub.add_parser("stats", help="Show statistics about the indexed project.")
|
|
380
|
+
_add_root_arg(p)
|
|
381
|
+
p.set_defaults(func=_cmd_stats)
|
|
382
|
+
|
|
383
|
+
p = sub.add_parser("choose-tool", help="Show which tool a natural-language query maps to.")
|
|
384
|
+
p.add_argument("query", help="Natural-language query.")
|
|
385
|
+
p.set_defaults(func=_cmd_choose_tool)
|
|
386
|
+
|
|
387
|
+
p = sub.add_parser(
|
|
388
|
+
"init",
|
|
389
|
+
help="Bind a directory as a HonestCode project (.honestcode/).",
|
|
390
|
+
)
|
|
391
|
+
p.add_argument("path", nargs="?", default=None, help="Project directory (default: cwd).")
|
|
392
|
+
p.add_argument("--force", action="store_true", help="Re-create an existing binding.")
|
|
393
|
+
p.set_defaults(func=_cmd_init)
|
|
394
|
+
|
|
395
|
+
p = sub.add_parser("uninit", help="Remove the .honestcode/ project binding.")
|
|
396
|
+
p.add_argument("path", nargs="?", default=None, help="Project directory (default: cwd).")
|
|
397
|
+
p.set_defaults(func=_cmd_uninit)
|
|
398
|
+
|
|
399
|
+
p = sub.add_parser(
|
|
400
|
+
"install",
|
|
401
|
+
help="Register the MCP server with a client (Cursor, VS Code, Claude Code).",
|
|
402
|
+
)
|
|
403
|
+
p.add_argument("path", nargs="?", default=None, help="Project directory (default: cwd).")
|
|
404
|
+
p.add_argument(
|
|
405
|
+
"--client",
|
|
406
|
+
choices=["cursor", "vscode", "claude"],
|
|
407
|
+
default=None,
|
|
408
|
+
help="Only configure this client (default: all detected).",
|
|
409
|
+
)
|
|
410
|
+
p.add_argument("--dry-run", action="store_true", help="Preview changes without writing.")
|
|
411
|
+
p.set_defaults(func=_cmd_install)
|
|
412
|
+
|
|
413
|
+
p = sub.add_parser("root", help="Print the detected project root (binding or git).")
|
|
414
|
+
p.add_argument("path", nargs="?", default=None, help="Start directory (default: cwd).")
|
|
415
|
+
p.set_defaults(func=_cmd_root)
|
|
416
|
+
|
|
417
|
+
return parser
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def main(argv: list[str] | None = None) -> int:
|
|
421
|
+
parser = _build_parser()
|
|
422
|
+
args = parser.parse_args(argv)
|
|
423
|
+
func: Callable[[argparse.Namespace], int] = args.func
|
|
424
|
+
return func(args)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
if __name__ == "__main__":
|
|
428
|
+
sys.exit(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Persistent graph storage for HonestCode.
|
|
2
|
+
|
|
3
|
+
The call graph (definitions + references) is expensive to rebuild — it needs
|
|
4
|
+
an AST pass over every source file. ``graph_store.GraphCache`` persists it to
|
|
5
|
+
SQLite next to the symbol index so graph queries read from disk instead of
|
|
6
|
+
re-parsing the project on every call, and a FTS5 virtual table backs fast
|
|
7
|
+
full-text search.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from honestcode.graph.graph_store import CallGraph, GraphCache
|
|
11
|
+
from honestcode.graph.watcher import ProjectWatcher
|
|
12
|
+
|
|
13
|
+
__all__ = ["CallGraph", "GraphCache", "ProjectWatcher"]
|