agentfront 0.9.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.
Files changed (50) hide show
  1. agentfront/__init__.py +13 -0
  2. agentfront/__main__.py +8 -0
  3. agentfront/_brand.py +28 -0
  4. agentfront/cite/__init__.py +19 -0
  5. agentfront/cite/_engine.py +181 -0
  6. agentfront/cite/references/python-cli/AGENT.md +98 -0
  7. agentfront/cite/references/python-cli/MANIFEST.json +65 -0
  8. agentfront/cite/references/python-cli/tests/test_cli.py +46 -0
  9. agentfront/cite/references/python-cli/{{slug}}/__init__.py +12 -0
  10. agentfront/cite/references/python-cli/{{slug}}/__main__.py +10 -0
  11. agentfront/cite/references/python-cli/{{slug}}/cli/__init__.py +84 -0
  12. agentfront/cite/references/python-cli/{{slug}}/cli/_commands/__init__.py +0 -0
  13. agentfront/cite/references/python-cli/{{slug}}/cli/_commands/explain.py +38 -0
  14. agentfront/cite/references/python-cli/{{slug}}/cli/_commands/learn.py +81 -0
  15. agentfront/cite/references/python-cli/{{slug}}/cli/_errors.py +41 -0
  16. agentfront/cite/references/python-cli/{{slug}}/cli/_output.py +53 -0
  17. agentfront/cite/references/python-cli/{{slug}}/explain/__init__.py +24 -0
  18. agentfront/cite/references/python-cli/{{slug}}/explain/catalog.py +66 -0
  19. agentfront/cli/__init__.py +160 -0
  20. agentfront/cli/_commands/__init__.py +0 -0
  21. agentfront/cli/_commands/cli.py +198 -0
  22. agentfront/cli/_commands/doctor.py +564 -0
  23. agentfront/cli/_commands/explain.py +38 -0
  24. agentfront/cli/_commands/learn.py +130 -0
  25. agentfront/cli/_commands/overview.py +51 -0
  26. agentfront/cli/_errors.py +42 -0
  27. agentfront/cli/_output.py +58 -0
  28. agentfront/doctor/__init__.py +66 -0
  29. agentfront/doctor/_self_checks.py +347 -0
  30. agentfront/doctor/fixes.py +73 -0
  31. agentfront/explain/__init__.py +28 -0
  32. agentfront/explain/catalog.py +390 -0
  33. agentfront/overview/__init__.py +136 -0
  34. agentfront/overview/cli_surface.py +460 -0
  35. agentfront/rubric/__init__.py +53 -0
  36. agentfront/rubric/_runner.py +102 -0
  37. agentfront/rubric/_types.py +70 -0
  38. agentfront/rubric/checks/__init__.py +0 -0
  39. agentfront/rubric/checks/doctor.py +270 -0
  40. agentfront/rubric/checks/errors.py +107 -0
  41. agentfront/rubric/checks/explain_cmd.py +87 -0
  42. agentfront/rubric/checks/json_output.py +97 -0
  43. agentfront/rubric/checks/learnability.py +74 -0
  44. agentfront/rubric/checks/overview_cmd.py +181 -0
  45. agentfront/rubric/checks/structure.py +297 -0
  46. agentfront-0.9.0.dist-info/METADATA +93 -0
  47. agentfront-0.9.0.dist-info/RECORD +50 -0
  48. agentfront-0.9.0.dist-info/WHEEL +4 -0
  49. agentfront-0.9.0.dist-info/entry_points.txt +3 -0
  50. agentfront-0.9.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,81 @@
1
+ """``{{project_name}} learn`` — the learnability affordance (shape-adapt).
2
+
3
+ Update the text to describe {{project_name}} specifically. Must satisfy the
4
+ agent-first rubric: >=200 chars and mention purpose, command map, exit
5
+ codes, --json, explain.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+
12
+ from {{module}} import __version__
13
+ from {{module}}.cli._output import emit_result
14
+
15
+ _TEXT = """\
16
+ {{project_name}} — <one-line tagline>.
17
+
18
+ Purpose
19
+ -------
20
+ <Describe the tool's job. Write for an agent reader: concrete and terse.>
21
+
22
+ Commands
23
+ --------
24
+ {{project_name}} learn Print this self-teaching prompt. Supports --json.
25
+ {{project_name}} explain <path>... Print markdown docs for any noun/verb path.
26
+ Supports --json.
27
+ # Add your noun-verb entries here.
28
+
29
+ Machine-readable output
30
+ -----------------------
31
+ Every command that produces a listing or report supports --json. Errors in
32
+ JSON mode emit {"code", "message", "remediation"} to stderr. Stdout and
33
+ stderr are never mixed.
34
+
35
+ Exit-code policy
36
+ ----------------
37
+ 0 success
38
+ 1 user-input error (bad flag, bad path, missing arg)
39
+ 2 environment / setup error
40
+ 3+ reserved
41
+
42
+ More detail
43
+ -----------
44
+ {{project_name}} explain {{project_name}}
45
+ """
46
+
47
+
48
+ def _as_json_payload() -> dict[str, object]:
49
+ return {
50
+ "tool": "{{project_name}}",
51
+ "version": __version__,
52
+ "purpose": "<describe purpose>",
53
+ "commands": [
54
+ {"path": ["learn"], "summary": "Self-teaching prompt."},
55
+ {"path": ["explain"], "summary": "Markdown docs by path."},
56
+ ],
57
+ "exit_codes": {
58
+ "0": "success",
59
+ "1": "user-input error",
60
+ "2": "environment/setup error",
61
+ },
62
+ "json_support": True,
63
+ "explain_pointer": "{{project_name}} explain <path>",
64
+ }
65
+
66
+
67
+ def cmd_learn(args: argparse.Namespace) -> int:
68
+ if getattr(args, "json", False):
69
+ emit_result(_as_json_payload(), json_mode=True)
70
+ else:
71
+ emit_result(_TEXT, json_mode=False)
72
+ return 0
73
+
74
+
75
+ def register(sub: argparse._SubParsersAction) -> None:
76
+ p = sub.add_parser(
77
+ "learn",
78
+ help="Print a structured self-teaching prompt for agent consumers.",
79
+ )
80
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
81
+ p.set_defaults(func=cmd_learn)
@@ -0,0 +1,41 @@
1
+ """AfiError and exit-code policy (stable-contract — copy verbatim).
2
+
3
+ Every failure inside {{project_name}} raises :class:`AfiError`. The CLI entry
4
+ point catches it and exits with :attr:`AfiError.code`. Guarantees:
5
+
6
+ * no Python traceback leaks to stderr;
7
+ * every error has shape ``{code, message, remediation}``;
8
+ * the exit-code policy is centralised.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+
15
+ # Exit-code policy (documented in ``{{project_name}} learn`` output).
16
+ # 0 = success
17
+ # 1 = user-input error (bad flag, bad path, missing arg)
18
+ # 2 = environment / setup error
19
+ # 3+ = reserved
20
+ EXIT_SUCCESS = 0
21
+ EXIT_USER_ERROR = 1
22
+ EXIT_ENV_ERROR = 2
23
+
24
+
25
+ @dataclass
26
+ class AfiError(Exception):
27
+ """Structured error with a remediation hint for agents."""
28
+
29
+ code: int
30
+ message: str
31
+ remediation: str = ""
32
+
33
+ def __post_init__(self) -> None:
34
+ super().__init__(self.message)
35
+
36
+ def to_dict(self) -> dict[str, object]:
37
+ return {
38
+ "code": self.code,
39
+ "message": self.message,
40
+ "remediation": self.remediation,
41
+ }
@@ -0,0 +1,53 @@
1
+ """stdout / stderr helpers with a strict split (stable-contract).
2
+
3
+ Rule: **results go to stdout, diagnostics and errors go to stderr.** Agents
4
+ parsing output can rely on this invariant. JSON mode routes structured
5
+ payloads to the same streams — 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 {{module}}.cli._errors import AfiError
15
+
16
+
17
+ def emit_result(data: Any, *, json_mode: bool, stream: TextIO | None = None) -> None:
18
+ """Write a command result to stdout (or ``stream``)."""
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: AfiError, *, json_mode: bool, stream: TextIO | None = None) -> None:
31
+ """Write an :class:`AfiError` to stderr.
32
+
33
+ Text mode renders as two lines when a remediation is present::
34
+
35
+ error: <message>
36
+ hint: <remediation>
37
+
38
+ The ``hint:`` prefix is required by the agent-first error rubric.
39
+ """
40
+ s = stream if stream is not None else sys.stderr
41
+ if json_mode:
42
+ json.dump(err.to_dict(), s, ensure_ascii=False)
43
+ s.write("\n")
44
+ return
45
+ s.write(f"error: {err.message}\n")
46
+ if err.remediation:
47
+ s.write(f"hint: {err.remediation}\n")
48
+
49
+
50
+ def emit_diagnostic(message: str, *, stream: TextIO | None = None) -> None:
51
+ """Write a human diagnostic (progress, summary) to stderr."""
52
+ s = stream if stream is not None else sys.stderr
53
+ s.write(message if message.endswith("\n") else message + "\n")
@@ -0,0 +1,24 @@
1
+ """Explain catalog — markdown keyed by command-path tuples (stable-contract).
2
+
3
+ Every noun/verb registered in the CLI should have a catalog entry.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from {{module}}.cli._errors import EXIT_USER_ERROR, AfiError
9
+ from {{module}}.explain.catalog import ENTRIES
10
+
11
+
12
+ def resolve(path: tuple[str, ...]) -> str:
13
+ if path in ENTRIES:
14
+ return ENTRIES[path]
15
+ display = " ".join(path) if path else "<root>"
16
+ raise AfiError(
17
+ code=EXIT_USER_ERROR,
18
+ message=f"no explain entry for: {display}",
19
+ remediation="list known entries with: {{project_name}} explain {{project_name}}",
20
+ )
21
+
22
+
23
+ def known_paths() -> list[tuple[str, ...]]:
24
+ return list(ENTRIES.keys())
@@ -0,0 +1,66 @@
1
+ """Markdown catalog for ``{{project_name}} explain <path>``.
2
+
3
+ Each entry is verbatim markdown. Keys are command-path tuples. The empty
4
+ tuple and ``("{{project_name}}",)`` both resolve to the root entry.
5
+
6
+ Keep bodies self-contained: an agent reading one entry should get enough
7
+ context without chaining reads.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ _ROOT = """\
13
+ # {{project_name}}
14
+
15
+ <One-paragraph description of {{project_name}} aimed at an agent reader.>
16
+
17
+ ## Verbs
18
+
19
+ - `{{project_name}} learn` — structured self-teaching prompt.
20
+ - `{{project_name}} explain <path>` — markdown docs for any noun/verb.
21
+
22
+ ## Exit-code policy
23
+
24
+ - `0` success
25
+ - `1` user-input error
26
+ - `2` environment / setup error
27
+ - `3+` reserved
28
+
29
+ ## See also
30
+
31
+ - `{{project_name}} explain learn`
32
+ - `{{project_name}} explain explain`
33
+ """
34
+
35
+ _LEARN = """\
36
+ # {{project_name}} learn
37
+
38
+ Prints a structured self-teaching prompt covering {{project_name}}'s purpose,
39
+ command map, exit-code policy, `--json` support, and `explain` pointer.
40
+
41
+ ## Usage
42
+
43
+ {{project_name}} learn
44
+ {{project_name}} learn --json
45
+ """
46
+
47
+ _EXPLAIN = """\
48
+ # {{project_name}} explain <path>
49
+
50
+ Prints markdown documentation for any noun/verb path. Unlike `--help`
51
+ (terse, positional), `explain` is global and addressable by path.
52
+
53
+ ## Usage
54
+
55
+ {{project_name}} explain {{project_name}}
56
+ {{project_name}} explain learn
57
+ {{project_name}} explain --json <path>
58
+ """
59
+
60
+
61
+ ENTRIES: dict[tuple[str, ...], str] = {
62
+ (): _ROOT,
63
+ ("{{project_name}}",): _ROOT,
64
+ ("learn",): _LEARN,
65
+ ("explain",): _EXPLAIN,
66
+ }
@@ -0,0 +1,160 @@
1
+ """Unified CLI entry point for agentfront.
2
+
3
+ Noun-based command groups and globals are registered here. Top-level globals
4
+ (``learn``, ``explain``) live under :mod:`agentfront.cli._commands`; per-noun groups
5
+ (``cli``, later ``mcp``, ``site``) are registered via their own ``register()``
6
+ functions following the same pattern.
7
+
8
+ Error propagation contract
9
+ --------------------------
10
+ Every handler raises :class:`agentfront.cli._errors.AfiError` on failure; the
11
+ top-level ``main()`` catches it via :func:`_dispatch` and routes through
12
+ :mod:`agentfront.cli._output`. Unknown exceptions are wrapped into an ``AfiError``
13
+ so no Python traceback leaks to stderr.
14
+
15
+ Argparse errors (unknown verb, missing required arg) also route through our
16
+ structured format — ``_AgentfrontArgumentParser`` overrides ``.error()``. The
17
+ subparsers are built with ``parser_class=_AgentfrontArgumentParser`` so subparser
18
+ errors follow the same path. Whether the error is emitted as text or JSON
19
+ depends on whether ``--json`` appears in the raw argv (:func:`main` sets
20
+ ``_AgentfrontArgumentParser._json_hint`` before ``parse_args``).
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import sys
27
+
28
+ from agentfront import __version__, _brand
29
+ from agentfront.cli._errors import EXIT_USER_ERROR, AfiError
30
+ from agentfront.cli._output import emit_error
31
+
32
+ # Note: _commands submodules are imported lazily inside :func:`_build_parser`
33
+ # to avoid a circular dependency. agentfront.cite._engine imports agentfront.cli._errors;
34
+ # eagerly loading agentfront.cli._commands.cli (which imports agentfront.cite) at module
35
+ # init would create a cycle when agentfront.cite is the first-touched package.
36
+
37
+
38
+ class _AgentfrontArgumentParser(argparse.ArgumentParser):
39
+ """ArgumentParser that routes errors through :func:`emit_error`.
40
+
41
+ Argparse's default error handler writes ``prog: error: <msg>`` to stderr
42
+ and exits with code 2. That skips our AfiError plumbing and — crucially —
43
+ produces no ``hint:`` line, which would make agentfront itself fail the rubric's
44
+ error-propagation bundle. This subclass emits our structured error format
45
+ instead and exits with :attr:`EXIT_USER_ERROR`.
46
+
47
+ JSON mode: parse-time errors happen before ``args.json`` is populated, so
48
+ we rely on a class-level ``_json_hint`` that :func:`main` pre-populates
49
+ by scanning the raw argv for ``--json`` / ``--json=…``. Best-effort and
50
+ shared across all subparser instances (argparse's subparser factory
51
+ produces instances of the class but doesn't thread state).
52
+ """
53
+
54
+ _json_hint: bool = False
55
+
56
+ def error(self, message: str) -> None: # type: ignore[override]
57
+ err = AfiError(
58
+ code=EXIT_USER_ERROR,
59
+ message=message,
60
+ remediation=f"run '{self.prog} --help' to see valid arguments",
61
+ )
62
+ emit_error(err, json_mode=type(self)._json_hint)
63
+ raise SystemExit(err.code)
64
+
65
+
66
+ def _argv_has_json(argv: list[str] | None) -> bool:
67
+ tokens = argv if argv is not None else sys.argv[1:]
68
+ return any(t == "--json" or t.startswith("--json=") for t in tokens)
69
+
70
+
71
+ def _build_parser() -> argparse.ArgumentParser:
72
+ # Deferred imports (see module-level note): avoids agentfront.cite ↔ agentfront.cli cycle.
73
+ from agentfront.cli._commands import cli as _cli_group
74
+ from agentfront.cli._commands import doctor as _doctor_cmd
75
+ from agentfront.cli._commands import explain as _explain_cmd
76
+ from agentfront.cli._commands import learn as _learn_cmd
77
+ from agentfront.cli._commands import overview as _overview_cmd
78
+
79
+ parser = _AgentfrontArgumentParser(
80
+ prog=_brand.PROG,
81
+ description=f"{_brand.PROG} — Agent First Interface scaffolder",
82
+ )
83
+ parser.add_argument(
84
+ "--version",
85
+ action="version",
86
+ version=f"%(prog)s {__version__}",
87
+ )
88
+ # parser_class propagates to every subparser so their .error() routes
89
+ # through _AgentfrontArgumentParser too. Without this, `agentfront cli bogus --foo`
90
+ # would hit argparse's default error path (no hint: line, wrong code).
91
+ sub = parser.add_subparsers(dest="command", parser_class=_AgentfrontArgumentParser)
92
+
93
+ # Globals (top-level, not nested under a noun).
94
+ _learn_cmd.register(sub)
95
+ _explain_cmd.register(sub)
96
+ _overview_cmd.register(sub)
97
+ _doctor_cmd.register(sub)
98
+
99
+ # Noun groups.
100
+ _cli_group.register(sub)
101
+ # Future noun groups (mcp, site) will register here in v0.4 / v0.5.
102
+
103
+ return parser
104
+
105
+
106
+ def _dispatch(args: argparse.Namespace) -> int:
107
+ """Invoke the registered handler and translate exceptions to exit codes.
108
+
109
+ Handler protocol: a handler may return ``None`` (treated as success,
110
+ exit 0) or an ``int`` (used directly as the exit code). Failures MUST
111
+ raise :class:`AfiError`; any other exception is wrapped into one so no
112
+ Python traceback leaks.
113
+ """
114
+ json_mode = bool(getattr(args, "json", False))
115
+ try:
116
+ rc = args.func(args)
117
+ except AfiError as err:
118
+ emit_error(err, json_mode=json_mode)
119
+ return err.code
120
+ except Exception as err: # noqa: BLE001 - last-resort; wrap and route cleanly
121
+ wrapped = AfiError(
122
+ code=EXIT_USER_ERROR,
123
+ message=f"unexpected: {err.__class__.__name__}: {err}",
124
+ remediation=f"file a bug at {_brand.ISSUES_URL}",
125
+ )
126
+ emit_error(wrapped, json_mode=json_mode)
127
+ return wrapped.code
128
+ return rc if rc is not None else 0
129
+
130
+
131
+ def main(argv: list[str] | None = None) -> int:
132
+ # Pre-parse peek so argparse-level errors honour --json.
133
+ _AgentfrontArgumentParser._json_hint = _argv_has_json(argv)
134
+ parser = _build_parser()
135
+ args = parser.parse_args(argv)
136
+
137
+ if args.command is None:
138
+ parser.print_help()
139
+ return 0
140
+
141
+ return _dispatch(args)
142
+
143
+
144
+ def main_teken_alias(argv: list[str] | None = None) -> int:
145
+ """Entry point for the deprecated ``teken`` command.
146
+
147
+ The project was renamed ``teken`` → ``agentfront``. The ``teken`` console script is
148
+ retained as an alias; it prints a one-line deprecation note to **stderr**
149
+ (never stdout, so ``--json`` output stays clean) and forwards to :func:`main`.
150
+ """
151
+ print(
152
+ f"deprecated: the '{_brand.LEGACY_PROG}' command is now '{_brand.PROG}'; "
153
+ f"'{_brand.LEGACY_PROG}' will be removed in a future release.",
154
+ file=sys.stderr,
155
+ )
156
+ return main(argv)
157
+
158
+
159
+ if __name__ == "__main__":
160
+ sys.exit(main())
File without changes
@@ -0,0 +1,198 @@
1
+ """The ``cli`` noun group — verbs ``cite``, ``doctor``, ``overview``.
2
+
3
+ ``agentfront cli cite`` — drop the agent-first CLI reference tree into the target
4
+ project under ``.agentfront/reference/<lang>-cli/``.
5
+ ``agentfront cli doctor`` — run the seven-bundle rubric against a target CLI
6
+ and surface inconsistencies with actionable remediation
7
+ (replaces ``agentfront cli verify``; ``--fix`` applies any
8
+ auto-fixable handlers).
9
+ ``agentfront cli overview`` — read-only descriptive snapshot of a target CLI.
10
+
11
+ ``agentfront cli verify`` is kept as a deprecated alias that forwards to
12
+ ``cli doctor`` for one minor cycle; removed in v0.6.0.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ from pathlib import Path
19
+
20
+ from agentfront.cite import SUPPORTED_LANGS, emit_reference
21
+ from agentfront.cli._commands.doctor import cmd_cli_doctor, cmd_cli_verify_deprecated
22
+ from agentfront.cli._output import emit_result
23
+ from agentfront.overview import build as build_overview
24
+ from agentfront.overview import to_json_dict, to_markdown
25
+
26
+ _JSON_HELP = "Emit structured JSON."
27
+ _PATH_HELP = "Target project path (default: .)."
28
+ _STRICT_HELP = "Treat warnings as failures (non-zero exit on any not-passed check)."
29
+
30
+
31
+ def cmd_cite(args: argparse.Namespace) -> None:
32
+ """Handler for ``agentfront cli cite``.
33
+
34
+ Returns ``None`` on success (treated as ``EXIT_SUCCESS`` by
35
+ :func:`agentfront.cli._dispatch`); every failure path raises
36
+ :class:`AfiError`.
37
+ """
38
+ target_path = Path(args.path).resolve()
39
+ lang = args.lang
40
+ out = Path(args.out).resolve() if args.out else None
41
+ report = emit_reference(target_path, lang=lang, out=out)
42
+ json_mode = bool(getattr(args, "json", False))
43
+ if json_mode:
44
+ emit_result(report.to_dict(), json_mode=True)
45
+ return
46
+
47
+ lines = [
48
+ f"Wrote {report.written_count} files to {report.out}",
49
+ (
50
+ "Added `.agentfront/` to .gitignore"
51
+ if report.gitignore_updated
52
+ else ".gitignore already ignores `.agentfront/`"
53
+ ),
54
+ "",
55
+ "Next steps:",
56
+ ]
57
+ for i, step in enumerate(report.describe_next_steps(), start=1):
58
+ lines.append(f" {i}. {step}")
59
+ lines.extend(
60
+ [
61
+ "",
62
+ "For details on any step: agentfront explain cli cite",
63
+ "For the rubric itself: agentfront explain cli doctor",
64
+ ]
65
+ )
66
+ emit_result("\n".join(lines), json_mode=False)
67
+
68
+
69
+ def cmd_cli_overview(args: argparse.Namespace) -> int:
70
+ """Handler for ``agentfront cli overview [path]``.
71
+
72
+ Read-only descriptive snapshot. If ``path`` is missing or points at a
73
+ project without a detectable CLI surface, emits the overview of agentfront's
74
+ own scaffolded template (the "zero-target default").
75
+ """
76
+ raw = getattr(args, "path", None)
77
+ path: Path | None = Path(raw).resolve() if raw else None
78
+ report = build_overview("cli", path)
79
+ json_mode = bool(getattr(args, "json", False))
80
+ if json_mode:
81
+ emit_result(to_json_dict(report), json_mode=True)
82
+ else:
83
+ emit_result(to_markdown(report), json_mode=False)
84
+ return 0
85
+
86
+
87
+ def register(sub: argparse._SubParsersAction) -> None:
88
+ cli_parser = sub.add_parser(
89
+ "cli",
90
+ help="CLI-related commands: cite a reference tree, doctor against the rubric.",
91
+ )
92
+ cli_sub = cli_parser.add_subparsers(dest="cli_command")
93
+
94
+ cite = cli_sub.add_parser(
95
+ "cite",
96
+ help="Emit the agent-first CLI reference tree into <path>/.agentfront/reference/.",
97
+ )
98
+ cite.add_argument("path", nargs="?", default=".", help=_PATH_HELP)
99
+ cite.add_argument(
100
+ "--lang",
101
+ default="python",
102
+ choices=list(SUPPORTED_LANGS),
103
+ help="Reference language (default: python).",
104
+ )
105
+ cite.add_argument(
106
+ "--out",
107
+ default=None,
108
+ help="Override output directory (default: <path>/.agentfront/reference/<lang>-cli/).",
109
+ )
110
+ cite.add_argument("--json", action="store_true", help=_JSON_HELP)
111
+ cite.set_defaults(func=cmd_cite)
112
+
113
+ doctor = cli_sub.add_parser(
114
+ "doctor",
115
+ help=(
116
+ "Audit the CLI at <path> against the seven-bundle agent-first rubric "
117
+ "and surface remediations; --fix applies auto-fixable ones."
118
+ ),
119
+ )
120
+ # Default kept as None (not "."): the cwd fallback lives in cmd_cli_doctor
121
+ # so we can tell "no args" from "explicit path" and reject `--package`
122
+ # combined with an explicit path.
123
+ doctor.add_argument(
124
+ "path",
125
+ nargs="?",
126
+ default=None,
127
+ help=_PATH_HELP + " Omit and pass --package to audit by distribution name.",
128
+ )
129
+ doctor.add_argument(
130
+ "--package",
131
+ default=None,
132
+ metavar="NAME",
133
+ help=(
134
+ "Audit an editable-installed distribution by name (looks up "
135
+ "its source root via PEP 610 direct_url.json). Mutually "
136
+ "exclusive with the path positional."
137
+ ),
138
+ )
139
+ doctor.add_argument("--json", action="store_true", help=_JSON_HELP)
140
+ # --fix and --dry-run are alternatives: --dry-run previews what --fix
141
+ # would do. Mutually exclusive at the argparse layer so passing both
142
+ # gives a clear error instead of silent precedence.
143
+ fix_group = doctor.add_mutually_exclusive_group()
144
+ fix_group.add_argument(
145
+ "--fix",
146
+ action="store_true",
147
+ help="Apply auto-fixable remediations.",
148
+ )
149
+ fix_group.add_argument(
150
+ "--dry-run",
151
+ action="store_true",
152
+ dest="dry_run",
153
+ help="Preview which fixes would be applied without mutating.",
154
+ )
155
+ doctor.add_argument(
156
+ "--strict",
157
+ action="store_true",
158
+ help=_STRICT_HELP,
159
+ )
160
+ doctor.set_defaults(func=cmd_cli_doctor)
161
+
162
+ # Deprecated alias for one minor cycle. Mirror the v0.4 verify shape so
163
+ # CI and scripts that call `agentfront cli verify .` keep working; the deprecation
164
+ # diagnostic is emitted by cmd_cli_verify_deprecated to stderr.
165
+ verify = cli_sub.add_parser(
166
+ "verify",
167
+ help="(Deprecated) Alias for `agentfront cli doctor`. Removed in v0.6.0.",
168
+ )
169
+ verify.add_argument("path", nargs="?", default=".", help=_PATH_HELP)
170
+ verify.add_argument("--json", action="store_true", help=_JSON_HELP)
171
+ verify.add_argument(
172
+ "--strict",
173
+ action="store_true",
174
+ help=_STRICT_HELP,
175
+ )
176
+ verify.set_defaults(func=cmd_cli_verify_deprecated)
177
+
178
+ overview = cli_sub.add_parser(
179
+ "overview",
180
+ help="Read-only descriptive snapshot of the CLI at <path> (defaults: agentfront template).",
181
+ )
182
+ overview.add_argument(
183
+ "path",
184
+ nargs="?",
185
+ default=None,
186
+ help=(
187
+ "Target project path. If omitted (or the target has no CLI surface), "
188
+ "describe agentfront's default scaffolded template."
189
+ ),
190
+ )
191
+ overview.add_argument("--json", action="store_true", help=_JSON_HELP)
192
+ overview.set_defaults(func=cmd_cli_overview)
193
+
194
+ def _no_verb(_args: argparse.Namespace) -> int:
195
+ cli_parser.print_help()
196
+ return 0
197
+
198
+ cli_parser.set_defaults(func=_no_verb)