kata-cli 0.7.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.
- kata_cli-0.7.0.dist-info/METADATA +36 -0
- kata_cli-0.7.0.dist-info/RECORD +33 -0
- kata_cli-0.7.0.dist-info/WHEEL +4 -0
- kata_cli-0.7.0.dist-info/entry_points.txt +3 -0
- kata_cli-0.7.0.dist-info/licenses/LICENSE +21 -0
- seer/__init__.py +34 -0
- seer/__main__.py +8 -0
- seer/cli/__init__.py +117 -0
- seer/cli/_commands/__init__.py +1 -0
- seer/cli/_commands/classify.py +40 -0
- seer/cli/_commands/explain.py +44 -0
- seer/cli/_commands/grep.py +44 -0
- seer/cli/_commands/learn.py +49 -0
- seer/cli/_commands/recent.py +52 -0
- seer/cli/_commands/whoami.py +42 -0
- seer/cli/_errors.py +59 -0
- seer/cli/_output.py +47 -0
- seer/lookup/__init__.py +25 -0
- seer/lookup/ast_scope.py +74 -0
- seer/lookup/classify.py +301 -0
- seer/lookup/grep_context.py +160 -0
- seer/lookup/recent_outline.py +304 -0
- seer/lookup/render.py +41 -0
- seer/repo/__init__.py +9 -0
- seer/repo/__main__.py +228 -0
- seer/repo/config.py +57 -0
- seer/repo/connections.py +298 -0
- seer/repo/detect.py +86 -0
- seer/repo/errors.py +81 -0
- seer/repo/graph.py +182 -0
- seer/repo/manifest.py +36 -0
- seer/repo/profile.py +700 -0
- seer/repo/render.py +470 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kata-cli
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: seer — codebase lookup and indexing for agent skills (greenfield AgentCulture sibling).
|
|
5
|
+
Project-URL: Homepage, https://github.com/agentculture/seer-cli
|
|
6
|
+
Project-URL: Issues, https://github.com/agentculture/seer-cli/issues
|
|
7
|
+
Author: AgentCulture
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# seer-cli
|
|
20
|
+
|
|
21
|
+
Codebase lookup and indexing for agent skills.
|
|
22
|
+
|
|
23
|
+
## What's here
|
|
24
|
+
|
|
25
|
+
- **`seer/`** — the package that will eventually expose the lookup
|
|
26
|
+
verbs. Greenfield: `learn` / `explain` / `whoami` are honest
|
|
27
|
+
placeholder stubs. See [`CLAUDE.md`](./CLAUDE.md) for build / test /
|
|
28
|
+
architecture details.
|
|
29
|
+
|
|
30
|
+
- **`experiments/scripts_eval/`** — A/B-test harness for the `repo-map`
|
|
31
|
+
skill (env-var-gated hooks, three-layer scoring, 5-repo round-1
|
|
32
|
+
corpus). Round 1 is the validation gate before the verb design
|
|
33
|
+
lands. See
|
|
34
|
+
[`experiments/scripts_eval/README.md`](./experiments/scripts_eval/README.md)
|
|
35
|
+
and
|
|
36
|
+
[`experiments/scripts_eval/RUNBOOK.md`](./experiments/scripts_eval/RUNBOOK.md).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
seer/__init__.py,sha256=JQ3XCmXRjUVkwA0swxrWgXzGa2e4purayRsLYwnT3g0,1300
|
|
2
|
+
seer/__main__.py,sha256=XUPM8sm0etIektS3zDQ8stjfTbdDjDZa_F1VajTKffI,136
|
|
3
|
+
seer/cli/__init__.py,sha256=TDSqi-LbGWN786epJ_DzgYWrLuRuqCebrFGAPYdXyzo,4076
|
|
4
|
+
seer/cli/_errors.py,sha256=mUhHlIlSFH0GsuTD0-oMjmJhjtWQiPseblhUIq6p2Vg,1953
|
|
5
|
+
seer/cli/_output.py,sha256=wjSLvrjf9xFjj0C_flV8bAxAAZAkpgox0LA4NF17w4E,1594
|
|
6
|
+
seer/cli/_commands/__init__.py,sha256=vMVWcay13DFXlUpLhsU-9ZYQ6HpZVerf_3jRXtjdvuA,83
|
|
7
|
+
seer/cli/_commands/classify.py,sha256=hQQTwstftzzW4c0rX2u7u65QIjXUcjKWT9HscZXx6hM,1394
|
|
8
|
+
seer/cli/_commands/explain.py,sha256=LnxkcGWTYXAq-fbSV4WfLJ8yXieGzHRwf_yQIeICt6A,1366
|
|
9
|
+
seer/cli/_commands/grep.py,sha256=M-Mhqz9kK2lBUEmWkk67_6HjraUuDqOxMk_y00_8WW0,1414
|
|
10
|
+
seer/cli/_commands/learn.py,sha256=yqtfOCsSwX-7rduHj0xRCHUtgtdjQep5u-5fgjnYRlg,1580
|
|
11
|
+
seer/cli/_commands/recent.py,sha256=yR0ImB3WOx7FR_phOL5rL-vn7BhiYQSTcEsGtVY90b4,1585
|
|
12
|
+
seer/cli/_commands/whoami.py,sha256=AjZt5W5ugbmUL1s0ZwQCbaLAiCOdeyEC4C6miiEkc50,1248
|
|
13
|
+
seer/lookup/__init__.py,sha256=u9q8wcBDSo472KKbhoQPHRPOM-sTBTpXY0CWKF_Bh4U,799
|
|
14
|
+
seer/lookup/ast_scope.py,sha256=IUgvBjBYlXuZtddK_Os2KxsZS9lCYGEE60i0z3fLMYM,2503
|
|
15
|
+
seer/lookup/classify.py,sha256=lJpnefVxgG9MrBIWjARJEJJCir6nqhMKDRdCn1xaVKI,10293
|
|
16
|
+
seer/lookup/grep_context.py,sha256=CpiEbvKtQQkvgRTDweGGtIU7KMYL7LZKSkuuVKzCR_8,5790
|
|
17
|
+
seer/lookup/recent_outline.py,sha256=D-teevbUHLs8P8N5n7Y8xa6FZA_PPGsR6v-lH9qs5b8,10592
|
|
18
|
+
seer/lookup/render.py,sha256=0wPrnj8dKOZr-_sEWFmuBQnBfcb8bKZq69fF0Amkz4c,1196
|
|
19
|
+
seer/repo/__init__.py,sha256=eHjI3F-h_eI56o9zHINW4SakRFu2G5CWaHLX12fquX4,276
|
|
20
|
+
seer/repo/__main__.py,sha256=kZdp2R-DaNur5w5sX7mUhAn3xnM0d-m4y_IhMqZJcPs,7654
|
|
21
|
+
seer/repo/config.py,sha256=N63dYJ8dKwrIS__pL3AHGFvDZq4_tHCAhfFy2MCMfYo,1857
|
|
22
|
+
seer/repo/connections.py,sha256=JdfX9JrWASZoPWwWN72xW10jwY4ixYJyYQg_qr-OAu8,9532
|
|
23
|
+
seer/repo/detect.py,sha256=zTfdqZAVP_n0RawPEd7zaTmwe9LAEtdpQNCAYCPB4CE,2781
|
|
24
|
+
seer/repo/errors.py,sha256=VaSFXB2_j-ZhuYjL2kszBbjRYS3a9YB_fL87zfHPKfY,2969
|
|
25
|
+
seer/repo/graph.py,sha256=r5AXfjq6dGxGcl0VC31BLoWXZbS6Hscj1W-r2CouHFk,5947
|
|
26
|
+
seer/repo/manifest.py,sha256=2Vc7FvWBTqpXHuX9uKKYqP5LGpqmzRgdszlEcAFzgLo,1264
|
|
27
|
+
seer/repo/profile.py,sha256=BqwOwW_yV-8rW9lNx-8sYrYZouImIdHHPQaePCznZdc,25299
|
|
28
|
+
seer/repo/render.py,sha256=wbfkDG8Wfyp5wzFHZ-aD1u1sl7mMVl_gOzWDs_r0wWc,16205
|
|
29
|
+
kata_cli-0.7.0.dist-info/METADATA,sha256=2sRMh2hZWzRADX_F2ygr-FDPEnhBSFgUcaDW11hdJDg,1364
|
|
30
|
+
kata_cli-0.7.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
31
|
+
kata_cli-0.7.0.dist-info/entry_points.txt,sha256=wXuXJDkd57Utvp9VV6iqRp6I3fojmwdNHcqiHTqx4r4,60
|
|
32
|
+
kata_cli-0.7.0.dist-info/licenses/LICENSE,sha256=wCcdPywGtFXx1P8N0j0eEDINSWfSjrIsU7ds1YZl-MA,1069
|
|
33
|
+
kata_cli-0.7.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentCulture
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
seer/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""seer — codebase lookup and indexing for agent skills (greenfield AgentCulture sibling)."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, packages_distributions
|
|
4
|
+
from importlib.metadata import version as _v
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _resolve_version() -> str:
|
|
8
|
+
"""Resolve the installed-distribution version of the ``seer`` package.
|
|
9
|
+
|
|
10
|
+
The same source ships under multiple PyPI distribution names (``seer-cli``,
|
|
11
|
+
``kata-cli``, ``code-lens-cli``) per the lean dual-publish slice of issue
|
|
12
|
+
#17. Look up which distribution provides the ``seer`` top-level package
|
|
13
|
+
via ``importlib.metadata.packages_distributions()`` and return its
|
|
14
|
+
version. Falls back through a known-name allowlist if that lookup is
|
|
15
|
+
unavailable, and finally to ``0.0.0+local`` for editable installs without
|
|
16
|
+
metadata.
|
|
17
|
+
"""
|
|
18
|
+
dist_map = packages_distributions()
|
|
19
|
+
for dist in dist_map.get("seer") or []:
|
|
20
|
+
try:
|
|
21
|
+
return _v(dist)
|
|
22
|
+
except PackageNotFoundError:
|
|
23
|
+
continue
|
|
24
|
+
for fallback in ("seer-cli", "kata-cli", "code-lens-cli"):
|
|
25
|
+
try:
|
|
26
|
+
return _v(fallback)
|
|
27
|
+
except PackageNotFoundError:
|
|
28
|
+
continue
|
|
29
|
+
return "0.0.0+local" # pragma: no cover — editable install without metadata
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__version__ = _resolve_version()
|
|
33
|
+
|
|
34
|
+
__all__ = ["__version__"]
|
seer/__main__.py
ADDED
seer/cli/__init__.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Unified CLI entry point for seer.
|
|
2
|
+
|
|
3
|
+
Error-propagation contract: every handler raises
|
|
4
|
+
:class:`seer.cli._errors.SeerError` on failure; ``main()`` catches it via
|
|
5
|
+
:func:`_dispatch` and routes through :mod:`seer.cli._output`. Unknown
|
|
6
|
+
exceptions are wrapped into a ``SeerError`` so no Python traceback leaks.
|
|
7
|
+
|
|
8
|
+
Argparse errors (unknown verb, missing required arg) also route through the
|
|
9
|
+
structured format — :class:`_SeerArgumentParser` overrides ``.error()``.
|
|
10
|
+
Whether errors render as text or JSON depends on whether ``--json`` appears in
|
|
11
|
+
the raw argv (:func:`main` sets ``_SeerArgumentParser._json_hint`` before
|
|
12
|
+
``parse_args``).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# pylint: disable=duplicate-code # _dispatch mirrors seer.repo.__main__ by design
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
from seer import __version__
|
|
23
|
+
from seer.cli._errors import EXIT_USER_ERROR, SeerError
|
|
24
|
+
from seer.cli._output import emit_error
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _SeerArgumentParser(argparse.ArgumentParser):
|
|
28
|
+
"""ArgumentParser that routes errors through :func:`emit_error`."""
|
|
29
|
+
|
|
30
|
+
_json_hint: bool = False
|
|
31
|
+
|
|
32
|
+
def error(self, message: str) -> None: # type: ignore[override]
|
|
33
|
+
err = SeerError(
|
|
34
|
+
code=EXIT_USER_ERROR,
|
|
35
|
+
message=message,
|
|
36
|
+
remediation=f"run '{self.prog} --help' to see valid arguments",
|
|
37
|
+
)
|
|
38
|
+
emit_error(err, json_mode=type(self)._json_hint)
|
|
39
|
+
raise SystemExit(err.code)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _argv_has_json(argv: list[str] | None) -> bool:
|
|
43
|
+
tokens = argv if argv is not None else sys.argv[1:]
|
|
44
|
+
return any(t == "--json" or t.startswith("--json=") for t in tokens)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
48
|
+
parser = _SeerArgumentParser(
|
|
49
|
+
prog="seer",
|
|
50
|
+
description="seer — codebase lookup and indexing for agent skills (greenfield).",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--version",
|
|
54
|
+
action="version",
|
|
55
|
+
version=f"%(prog)s {__version__}",
|
|
56
|
+
)
|
|
57
|
+
sub = parser.add_subparsers(dest="command", parser_class=_SeerArgumentParser)
|
|
58
|
+
|
|
59
|
+
# pylint: disable=import-outside-toplevel
|
|
60
|
+
from seer.cli._commands import classify as _classify_cmd
|
|
61
|
+
from seer.cli._commands import explain as _explain_cmd
|
|
62
|
+
from seer.cli._commands import grep as _grep_cmd
|
|
63
|
+
from seer.cli._commands import learn as _learn_cmd
|
|
64
|
+
from seer.cli._commands import recent as _recent_cmd
|
|
65
|
+
from seer.cli._commands import whoami as _whoami_cmd
|
|
66
|
+
|
|
67
|
+
# pylint: enable=import-outside-toplevel
|
|
68
|
+
|
|
69
|
+
_learn_cmd.register(sub)
|
|
70
|
+
_explain_cmd.register(sub)
|
|
71
|
+
_whoami_cmd.register(sub)
|
|
72
|
+
_classify_cmd.register(sub)
|
|
73
|
+
_grep_cmd.register(sub)
|
|
74
|
+
_recent_cmd.register(sub)
|
|
75
|
+
|
|
76
|
+
return parser
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _dispatch(args: argparse.Namespace) -> int: # pylint: disable=duplicate-code
|
|
80
|
+
"""Invoke the registered handler and translate exceptions to exit codes.
|
|
81
|
+
|
|
82
|
+
A handler may return ``None`` (treated as success, exit 0) or an ``int``
|
|
83
|
+
used directly as the exit code. Failures MUST raise :class:`SeerError`;
|
|
84
|
+
any other exception is wrapped so no Python traceback leaks.
|
|
85
|
+
"""
|
|
86
|
+
json_mode = bool(getattr(args, "json", False))
|
|
87
|
+
try:
|
|
88
|
+
rc = args.func(args)
|
|
89
|
+
except SeerError as err:
|
|
90
|
+
emit_error(err, json_mode=json_mode)
|
|
91
|
+
return err.code
|
|
92
|
+
except Exception as err: # noqa: BLE001 # pylint: disable=broad-exception-caught
|
|
93
|
+
wrapped = SeerError(
|
|
94
|
+
code=EXIT_USER_ERROR,
|
|
95
|
+
message=f"unexpected: {err.__class__.__name__}: {err}",
|
|
96
|
+
remediation="file a bug at https://github.com/agentculture/seer-cli/issues",
|
|
97
|
+
)
|
|
98
|
+
emit_error(wrapped, json_mode=json_mode)
|
|
99
|
+
return wrapped.code
|
|
100
|
+
return rc if rc is not None else 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main(argv: list[str] | None = None) -> int:
|
|
104
|
+
"""Parse *argv* (defaults to ``sys.argv[1:]``) and dispatch to a verb handler."""
|
|
105
|
+
_SeerArgumentParser._json_hint = _argv_has_json(argv) # pylint: disable=protected-access
|
|
106
|
+
parser = _build_parser()
|
|
107
|
+
args = parser.parse_args(argv)
|
|
108
|
+
|
|
109
|
+
if args.command is None:
|
|
110
|
+
parser.print_help()
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
return _dispatch(args)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""seer CLI verb modules — one module per verb, each exposing ``register()``."""
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""``seer classify [path]`` — project-type classifier.
|
|
2
|
+
|
|
3
|
+
Returns a deterministic list of tags describing what kind of project the
|
|
4
|
+
repo at *path* is (cli / library / dockerized / tested / packaged-pypi / …),
|
|
5
|
+
each paired with concrete file-grounded evidence.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# pylint: disable=duplicate-code # verb-registration boilerplate
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from seer.cli._output import emit_result
|
|
16
|
+
from seer.lookup.classify import classify
|
|
17
|
+
from seer.lookup.render import render_classify_markdown
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def cmd_classify(args: argparse.Namespace) -> int:
|
|
21
|
+
"""Handle the ``classify`` verb."""
|
|
22
|
+
path = Path(args.path).resolve()
|
|
23
|
+
data = classify(path)
|
|
24
|
+
json_mode = bool(getattr(args, "json", False))
|
|
25
|
+
if json_mode:
|
|
26
|
+
emit_result({"ok": True, "data": data}, json_mode=True)
|
|
27
|
+
else:
|
|
28
|
+
emit_result(render_classify_markdown(data), json_mode=False)
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
33
|
+
"""Register the ``classify`` sub-command on *sub*."""
|
|
34
|
+
p = sub.add_parser(
|
|
35
|
+
"classify",
|
|
36
|
+
help="Classify a repo by project-type tags (cli / library / dockerized / …).",
|
|
37
|
+
)
|
|
38
|
+
p.add_argument("path", nargs="?", default=".", help="Path to the repo (default: cwd).")
|
|
39
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
40
|
+
p.set_defaults(func=cmd_classify)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""``seer explain`` — placeholder verb.
|
|
2
|
+
|
|
3
|
+
See :mod:`seer.cli._commands.learn` for why the verbs are stubs. ``explain``
|
|
4
|
+
will eventually print docs for a given topic / command path; today it prints
|
|
5
|
+
an honest "not yet implemented" line.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# pylint: disable=duplicate-code # intentional: three stub verbs share the same structure
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
from seer import __version__
|
|
15
|
+
from seer.cli._output import emit_result
|
|
16
|
+
|
|
17
|
+
_TEXT = "seer explain — not yet implemented; seer is greenfield. See CLAUDE.md."
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _json_payload() -> dict[str, object]:
|
|
21
|
+
return {
|
|
22
|
+
"tool": "seer",
|
|
23
|
+
"version": __version__,
|
|
24
|
+
"status": "greenfield",
|
|
25
|
+
"verb": "explain",
|
|
26
|
+
"message": _TEXT,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def cmd_explain(args: argparse.Namespace) -> int:
|
|
31
|
+
"""Handle the ``explain`` verb — print status and return 0."""
|
|
32
|
+
json_mode = bool(getattr(args, "json", False))
|
|
33
|
+
if json_mode:
|
|
34
|
+
emit_result(_json_payload(), json_mode=True)
|
|
35
|
+
else:
|
|
36
|
+
emit_result(_TEXT, json_mode=False)
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
41
|
+
"""Register the ``explain`` sub-command on *sub*."""
|
|
42
|
+
p = sub.add_parser("explain", help="Explain a seer topic or command (stub).")
|
|
43
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
44
|
+
p.set_defaults(func=cmd_explain)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""``seer grep <pattern> [path]`` — AST-scope-augmented ripgrep search.
|
|
2
|
+
|
|
3
|
+
Runs ``rg --json <pattern> <path>`` and pairs every match with the enclosing
|
|
4
|
+
Python function or class name via the AST scope resolver.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pylint: disable=duplicate-code # verb-registration boilerplate
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from seer.cli._output import emit_result
|
|
15
|
+
from seer.lookup.grep_context import grep_with_context, render_grep_markdown
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def cmd_grep(args: argparse.Namespace) -> int:
|
|
19
|
+
"""Handle the ``grep`` verb."""
|
|
20
|
+
path = Path(args.path).resolve()
|
|
21
|
+
data = grep_with_context(args.pattern, path)
|
|
22
|
+
json_mode = bool(getattr(args, "json", False))
|
|
23
|
+
if json_mode:
|
|
24
|
+
emit_result(data, json_mode=True)
|
|
25
|
+
else:
|
|
26
|
+
emit_result(render_grep_markdown(data), json_mode=False)
|
|
27
|
+
return 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
31
|
+
"""Register the ``grep`` sub-command on *sub*."""
|
|
32
|
+
p = sub.add_parser(
|
|
33
|
+
"grep",
|
|
34
|
+
help="Search a codebase with rg and annotate each match with its AST scope.",
|
|
35
|
+
)
|
|
36
|
+
p.add_argument("pattern", help="Regex pattern to search for.")
|
|
37
|
+
p.add_argument(
|
|
38
|
+
"path",
|
|
39
|
+
nargs="?",
|
|
40
|
+
default=".",
|
|
41
|
+
help="File or directory to search (default: cwd).",
|
|
42
|
+
)
|
|
43
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
44
|
+
p.set_defaults(func=cmd_grep)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""``seer learn`` — placeholder verb.
|
|
2
|
+
|
|
3
|
+
seer is a greenfield AgentCulture sibling: the scaffold (package, CLI
|
|
4
|
+
chassis, CI, vendored skills) is in place but the codebase lookup and
|
|
5
|
+
indexing engine itself is not implemented yet. This verb prints an honest
|
|
6
|
+
status line so a probing agent or human gets a clear signal rather than a
|
|
7
|
+
misleading response.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# pylint: disable=duplicate-code # intentional: three stub verbs share the same structure
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
from seer import __version__
|
|
17
|
+
from seer.cli._output import emit_result
|
|
18
|
+
|
|
19
|
+
_TEXT = (
|
|
20
|
+
"seer — codebase lookup and indexing for agent skills. Not yet implemented; "
|
|
21
|
+
"seer is a greenfield AgentCulture sibling. See CLAUDE.md."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _json_payload() -> dict[str, object]:
|
|
26
|
+
return {
|
|
27
|
+
"tool": "seer",
|
|
28
|
+
"version": __version__,
|
|
29
|
+
"status": "greenfield",
|
|
30
|
+
"verb": "learn",
|
|
31
|
+
"message": _TEXT,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cmd_learn(args: argparse.Namespace) -> int:
|
|
36
|
+
"""Handle the ``learn`` verb — print status and return 0."""
|
|
37
|
+
json_mode = bool(getattr(args, "json", False))
|
|
38
|
+
if json_mode:
|
|
39
|
+
emit_result(_json_payload(), json_mode=True)
|
|
40
|
+
else:
|
|
41
|
+
emit_result(_TEXT, json_mode=False)
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def register(sub: argparse._SubParsersAction) -> None: # pylint: disable=duplicate-code
|
|
46
|
+
"""Register the ``learn`` sub-command on *sub*."""
|
|
47
|
+
p = sub.add_parser("learn", help="Print seer's self-teaching status line.")
|
|
48
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
49
|
+
p.set_defaults(func=cmd_learn)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""``seer recent [path] [-n N]`` — git commit log paired with AST symbol diffs.
|
|
2
|
+
|
|
3
|
+
Runs ``git log -n N`` in *path*, and for each commit pairs every changed file
|
|
4
|
+
with a structural symbol-diff at the AST level (functions/classes added /
|
|
5
|
+
removed / modified).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# pylint: disable=duplicate-code # verb-registration boilerplate
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from seer.cli._output import emit_result
|
|
16
|
+
from seer.lookup.recent_outline import recent_with_outline, render_recent_markdown
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def cmd_recent(args: argparse.Namespace) -> int:
|
|
20
|
+
"""Handle the ``recent`` verb."""
|
|
21
|
+
path = Path(args.path).resolve()
|
|
22
|
+
data = recent_with_outline(n=args.count, path=path)
|
|
23
|
+
json_mode = bool(getattr(args, "json", False))
|
|
24
|
+
if json_mode:
|
|
25
|
+
emit_result(data, json_mode=True)
|
|
26
|
+
else:
|
|
27
|
+
emit_result(render_recent_markdown(data), json_mode=False)
|
|
28
|
+
return 0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
32
|
+
"""Register the ``recent`` sub-command on *sub*."""
|
|
33
|
+
p = sub.add_parser(
|
|
34
|
+
"recent",
|
|
35
|
+
help="Show recent git commits paired with AST symbol diffs per file.",
|
|
36
|
+
)
|
|
37
|
+
p.add_argument(
|
|
38
|
+
"path",
|
|
39
|
+
nargs="?",
|
|
40
|
+
default=".",
|
|
41
|
+
help="Path to the git repository (default: cwd).",
|
|
42
|
+
)
|
|
43
|
+
p.add_argument(
|
|
44
|
+
"-n",
|
|
45
|
+
"--count",
|
|
46
|
+
type=int,
|
|
47
|
+
default=20,
|
|
48
|
+
metavar="N",
|
|
49
|
+
help="Number of commits to show (default: 20).",
|
|
50
|
+
)
|
|
51
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
52
|
+
p.set_defaults(func=cmd_recent)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""``seer whoami`` — placeholder verb.
|
|
2
|
+
|
|
3
|
+
See :mod:`seer.cli._commands.learn` for why the verbs are stubs. ``whoami``
|
|
4
|
+
will eventually be the smallest identity / auth probe; today it prints an
|
|
5
|
+
honest "not yet implemented" line.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
|
|
12
|
+
from seer import __version__
|
|
13
|
+
from seer.cli._output import emit_result
|
|
14
|
+
|
|
15
|
+
_TEXT = "seer — not yet implemented; seer is greenfield. See CLAUDE.md."
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _json_payload() -> dict[str, object]:
|
|
19
|
+
return {
|
|
20
|
+
"tool": "seer",
|
|
21
|
+
"version": __version__,
|
|
22
|
+
"status": "greenfield",
|
|
23
|
+
"verb": "whoami",
|
|
24
|
+
"message": _TEXT,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cmd_whoami(args: argparse.Namespace) -> int:
|
|
29
|
+
"""Handle the ``whoami`` verb — print status and return 0."""
|
|
30
|
+
json_mode = bool(getattr(args, "json", False))
|
|
31
|
+
if json_mode:
|
|
32
|
+
emit_result(_json_payload(), json_mode=True)
|
|
33
|
+
else:
|
|
34
|
+
emit_result(_TEXT, json_mode=False)
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
39
|
+
"""Register the ``whoami`` sub-command on *sub*."""
|
|
40
|
+
p = sub.add_parser("whoami", help="Print seer's identity probe (stub).")
|
|
41
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
42
|
+
p.set_defaults(func=cmd_whoami)
|
seer/cli/_errors.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""SeerError and exit-code policy.
|
|
2
|
+
|
|
3
|
+
Every failure inside seer raises :class:`SeerError`. The top-level
|
|
4
|
+
``main()`` catches it, formats via :mod:`seer.cli._output`, and exits with
|
|
5
|
+
:attr:`SeerError.code`. This centralises the exit-code policy and guarantees
|
|
6
|
+
no Python traceback leaks to stderr.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
# Exit-code policy:
|
|
14
|
+
# 0 = success
|
|
15
|
+
# 1 = user-input error (bad flag, missing required arg, unknown path)
|
|
16
|
+
# 2 = environment / setup error (tool not installed, file unreadable)
|
|
17
|
+
# 3 = internal bug (uncaught exception wrapped by _dispatch)
|
|
18
|
+
# 4+ = reserved for future categorisation
|
|
19
|
+
EXIT_SUCCESS = 0
|
|
20
|
+
EXIT_USER_ERROR = 1
|
|
21
|
+
EXIT_ENV_ERROR = 2
|
|
22
|
+
EXIT_INTERNAL = 3
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class SeerError(Exception):
|
|
27
|
+
"""Structured error raised within seer.
|
|
28
|
+
|
|
29
|
+
Fields:
|
|
30
|
+
code: exit code (see constants above)
|
|
31
|
+
message: one-sentence plain-English description of what went wrong
|
|
32
|
+
remediation: optional concrete next step for the user/agent
|
|
33
|
+
reason: optional root-cause sentence (what was tried, why it failed)
|
|
34
|
+
kind: optional short tag — "user_error" | "env_error" | "bug"
|
|
35
|
+
|
|
36
|
+
Renderers (:func:`seer.cli._output.emit_error`) skip empty optional
|
|
37
|
+
fields so older call sites that only set code+message+remediation keep
|
|
38
|
+
producing the same output.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
code: int
|
|
42
|
+
message: str
|
|
43
|
+
remediation: str = ""
|
|
44
|
+
reason: str = ""
|
|
45
|
+
kind: str = ""
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
super().__init__(self.message)
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> dict[str, object]:
|
|
51
|
+
"""Serialise to a plain dict suitable for JSON output."""
|
|
52
|
+
out: dict[str, object] = {"code": self.code, "message": self.message}
|
|
53
|
+
if self.kind:
|
|
54
|
+
out["kind"] = self.kind
|
|
55
|
+
if self.reason:
|
|
56
|
+
out["reason"] = self.reason
|
|
57
|
+
if self.remediation:
|
|
58
|
+
out["remediation"] = self.remediation
|
|
59
|
+
return out
|
seer/cli/_output.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""stdout / stderr helpers with a strict split.
|
|
2
|
+
|
|
3
|
+
Rule: **results go to stdout, diagnostics and errors go to stderr.** Agents
|
|
4
|
+
parsing seer output can rely on this invariant. JSON mode routes structured
|
|
5
|
+
payloads to the same streams — it never mixes them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Any, TextIO
|
|
13
|
+
|
|
14
|
+
from seer.cli._errors import SeerError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def emit_result(data: Any, *, json_mode: bool, stream: TextIO | None = None) -> None:
|
|
18
|
+
"""Write a command result to stdout (text or JSON), newline-terminated."""
|
|
19
|
+
s = stream if stream is not None else sys.stdout
|
|
20
|
+
if json_mode:
|
|
21
|
+
json.dump(data, s, ensure_ascii=False)
|
|
22
|
+
s.write("\n")
|
|
23
|
+
return
|
|
24
|
+
text = data if isinstance(data, str) else str(data)
|
|
25
|
+
s.write(text)
|
|
26
|
+
if not text.endswith("\n"):
|
|
27
|
+
s.write("\n")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def emit_error(err: SeerError, *, json_mode: bool, stream: TextIO | None = None) -> None:
|
|
31
|
+
"""Write a :class:`SeerError` to stderr (text or JSON)."""
|
|
32
|
+
s = stream if stream is not None else sys.stderr
|
|
33
|
+
if json_mode:
|
|
34
|
+
json.dump(err.to_dict(), s, ensure_ascii=False)
|
|
35
|
+
s.write("\n")
|
|
36
|
+
return
|
|
37
|
+
s.write(f"error: {err.message}\n")
|
|
38
|
+
if err.reason:
|
|
39
|
+
s.write(f"reason: {err.reason}\n")
|
|
40
|
+
if err.remediation:
|
|
41
|
+
s.write(f"hint: {err.remediation}\n")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def emit_diagnostic(message: str, *, stream: TextIO | None = None) -> None:
|
|
45
|
+
"""Write a human diagnostic (progress, summary) to stderr."""
|
|
46
|
+
s = stream if stream is not None else sys.stderr
|
|
47
|
+
s.write(message if message.endswith("\n") else message + "\n")
|