devague 0.3.2__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.
devague/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """devague — turns a vague feature idea into a buildable spec by working backwards."""
2
+
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version as _v
5
+
6
+ try:
7
+ __version__ = _v("devague")
8
+ except PackageNotFoundError: # pragma: no cover — editable install without metadata
9
+ __version__ = "0.0.0+local"
10
+
11
+ __all__ = ["__version__"]
devague/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Allow running devague as ``python -m devague``."""
2
+
3
+ import sys
4
+
5
+ from devague.cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -0,0 +1,123 @@
1
+ """Unified CLI entry point for devague.
2
+
3
+ Error-propagation contract: every handler raises
4
+ :class:`devague.cli._errors.DevagueError` on failure; ``main()`` catches it
5
+ via :func:`_dispatch` and routes through :mod:`devague.cli._output`. Unknown
6
+ exceptions are wrapped into a ``DevagueError`` so no Python traceback leaks.
7
+
8
+ Argparse errors (unknown verb, missing required arg) also route through the
9
+ structured format — :class:`_DevagueArgumentParser` overrides ``.error()``.
10
+ Whether errors render as text or JSON depends on whether ``--json`` appears in
11
+ the raw argv (:func:`main` sets ``_DevagueArgumentParser._json_hint`` before
12
+ ``parse_args``).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import sys
19
+
20
+ from devague import __version__
21
+ from devague.cli._errors import EXIT_USER_ERROR, DevagueError
22
+ from devague.cli._output import emit_error
23
+
24
+
25
+ class _DevagueArgumentParser(argparse.ArgumentParser):
26
+ """ArgumentParser that routes errors through :func:`emit_error`."""
27
+
28
+ _json_hint: bool = False
29
+
30
+ def error(self, message: str) -> None: # type: ignore[override]
31
+ err = DevagueError(
32
+ code=EXIT_USER_ERROR,
33
+ message=message,
34
+ remediation=f"run '{self.prog} --help' to see valid arguments",
35
+ )
36
+ emit_error(err, json_mode=type(self)._json_hint)
37
+ raise SystemExit(err.code)
38
+
39
+
40
+ def _argv_has_json(argv: list[str] | None) -> bool:
41
+ tokens = argv if argv is not None else sys.argv[1:]
42
+ return any(t == "--json" or t.startswith("--json=") for t in tokens)
43
+
44
+
45
+ def _build_parser() -> argparse.ArgumentParser:
46
+ parser = _DevagueArgumentParser(
47
+ prog="devague",
48
+ description="devague — turns a vague idea into a buildable spec by working backwards.",
49
+ )
50
+ parser.add_argument(
51
+ "--version",
52
+ action="version",
53
+ version=f"%(prog)s {__version__}",
54
+ )
55
+ sub = parser.add_subparsers(dest="command", parser_class=_DevagueArgumentParser)
56
+
57
+ from devague.cli._commands import capture as _capture_cmd
58
+ from devague.cli._commands import confirm as _confirm_cmd
59
+ from devague.cli._commands import converge as _converge_cmd
60
+ from devague.cli._commands import explain as _explain_cmd
61
+ from devague.cli._commands import export as _export_cmd
62
+ from devague.cli._commands import interrogate as _interrogate_cmd
63
+ from devague.cli._commands import learn as _learn_cmd
64
+ from devague.cli._commands import list_frames as _list_cmd
65
+ from devague.cli._commands import new as _new_cmd
66
+ from devague.cli._commands import park as _park_cmd
67
+ from devague.cli._commands import reject as _reject_cmd
68
+ from devague.cli._commands import show as _show_cmd
69
+
70
+ _learn_cmd.register(sub)
71
+ _explain_cmd.register(sub)
72
+ _new_cmd.register(sub)
73
+ _capture_cmd.register(sub)
74
+ _interrogate_cmd.register(sub)
75
+ _confirm_cmd.register(sub)
76
+ _reject_cmd.register(sub)
77
+ _park_cmd.register(sub)
78
+ _converge_cmd.register(sub)
79
+ _export_cmd.register(sub)
80
+ _show_cmd.register(sub)
81
+ _list_cmd.register(sub)
82
+
83
+ return parser
84
+
85
+
86
+ def _dispatch(args: argparse.Namespace) -> int:
87
+ """Invoke the registered handler and translate exceptions to exit codes.
88
+
89
+ A handler may return ``None`` (treated as success, exit 0) or an ``int``
90
+ used directly as the exit code. Failures MUST raise :class:`DevagueError`;
91
+ any other exception is wrapped so no Python traceback leaks.
92
+ """
93
+ json_mode = bool(getattr(args, "json", False))
94
+ try:
95
+ rc = args.func(args)
96
+ except DevagueError as err:
97
+ emit_error(err, json_mode=json_mode)
98
+ return err.code
99
+ except Exception as err: # noqa: BLE001 - last-resort; wrap and route cleanly
100
+ wrapped = DevagueError(
101
+ code=EXIT_USER_ERROR,
102
+ message=f"unexpected: {err.__class__.__name__}: {err}",
103
+ remediation="file a bug at https://github.com/agentculture/devague/issues",
104
+ )
105
+ emit_error(wrapped, json_mode=json_mode)
106
+ return wrapped.code
107
+ return rc if rc is not None else 0
108
+
109
+
110
+ def main(argv: list[str] | None = None) -> int:
111
+ _DevagueArgumentParser._json_hint = _argv_has_json(argv)
112
+ parser = _build_parser()
113
+ args = parser.parse_args(argv)
114
+
115
+ if args.command is None:
116
+ parser.print_help()
117
+ return 0
118
+
119
+ return _dispatch(args)
120
+
121
+
122
+ if __name__ == "__main__":
123
+ sys.exit(main())
File without changes
@@ -0,0 +1,31 @@
1
+ """``devague capture`` — record and classify a claim."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import store
8
+ from devague.cli._frames import resolve
9
+ from devague.cli._output import emit_result
10
+ from devague.frame import CLAIM_KINDS
11
+
12
+
13
+ def cmd_capture(args: argparse.Namespace) -> int:
14
+ frame = resolve(args.frame)
15
+ claim = frame.add_claim(args.kind, args.text, origin=args.origin)
16
+ store.save(frame)
17
+ if getattr(args, "json", False):
18
+ emit_result({"id": claim.id, "kind": claim.kind, "status": claim.status}, json_mode=True)
19
+ else:
20
+ emit_result(f"captured {claim.id} ({claim.kind}, {claim.status})", json_mode=False)
21
+ return 0
22
+
23
+
24
+ def register(sub: argparse._SubParsersAction) -> None:
25
+ p = sub.add_parser("capture", help="Record and classify a claim.")
26
+ p.add_argument("text", help="The claim text.")
27
+ p.add_argument("--kind", required=True, choices=CLAIM_KINDS, help="Claim kind.")
28
+ p.add_argument("--origin", choices=("user", "llm"), default="user", help="Who proposed it.")
29
+ p.add_argument("--frame", help="Frame slug (default: current).")
30
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
31
+ p.set_defaults(func=cmd_capture)
@@ -0,0 +1,38 @@
1
+ """``devague confirm`` — confirm a claim or honesty condition (user-only transition)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import store
8
+ from devague.cli._errors import EXIT_USER_ERROR, DevagueError
9
+ from devague.cli._frames import resolve
10
+ from devague.cli._output import emit_result
11
+
12
+
13
+ def _transition(args: argparse.Namespace, status: str) -> int:
14
+ frame = resolve(args.frame)
15
+ if not frame.set_status(args.id, status):
16
+ raise DevagueError(
17
+ EXIT_USER_ERROR,
18
+ f"no such claim or honesty condition: {args.id}",
19
+ "run 'devague show'",
20
+ )
21
+ store.save(frame)
22
+ if getattr(args, "json", False):
23
+ emit_result({"id": args.id, "status": status}, json_mode=True)
24
+ else:
25
+ emit_result(f"{args.id} -> {status}", json_mode=False)
26
+ return 0
27
+
28
+
29
+ def cmd_confirm(args: argparse.Namespace) -> int:
30
+ return _transition(args, "confirmed")
31
+
32
+
33
+ def register(sub: argparse._SubParsersAction) -> None:
34
+ p = sub.add_parser("confirm", help="Confirm a claim or honesty condition.")
35
+ p.add_argument("id", help="Claim id (c*) or honesty id (h*).")
36
+ p.add_argument("--frame", help="Frame slug (default: current).")
37
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
38
+ p.set_defaults(func=cmd_confirm)
@@ -0,0 +1,37 @@
1
+ """``devague converge`` — evaluate the convergence gate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import store
8
+ from devague.cli._frames import resolve
9
+ from devague.cli._output import emit_result
10
+ from devague.convergence import evaluate
11
+
12
+
13
+ def cmd_converge(args: argparse.Namespace) -> int:
14
+ frame = resolve(args.frame)
15
+ result = evaluate(frame)
16
+ if result.passed and frame.status == "drafting":
17
+ frame.status = "converged"
18
+ store.save(frame)
19
+ elif not result.passed and frame.status == "converged":
20
+ frame.status = "drafting"
21
+ store.save(frame)
22
+ if getattr(args, "json", False):
23
+ emit_result({"passed": result.passed, "missing": result.missing}, json_mode=True)
24
+ elif result.passed:
25
+ emit_result("converged ✓", json_mode=False)
26
+ else:
27
+ emit_result(
28
+ "not converged:\n" + "\n".join(f" - {m}" for m in result.missing), json_mode=False
29
+ )
30
+ return 0
31
+
32
+
33
+ def register(sub: argparse._SubParsersAction) -> None:
34
+ p = sub.add_parser("converge", help="Check whether the frame can export a spec.")
35
+ p.add_argument("--frame", help="Frame slug (default: current).")
36
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
37
+ p.set_defaults(func=cmd_converge)
@@ -0,0 +1,31 @@
1
+ """``devague explain <move>`` — print docs for a single move."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague.cli._commands.learn import MOVES
8
+ from devague.cli._errors import EXIT_USER_ERROR, DevagueError
9
+ from devague.cli._output import emit_result
10
+
11
+
12
+ def cmd_explain(args: argparse.Namespace) -> int:
13
+ desc = MOVES.get(args.move)
14
+ if desc is None:
15
+ raise DevagueError(
16
+ EXIT_USER_ERROR,
17
+ f"unknown move: {args.move}",
18
+ f"available moves: {', '.join(MOVES)}",
19
+ )
20
+ if getattr(args, "json", False):
21
+ emit_result({"move": args.move, "description": desc}, json_mode=True)
22
+ else:
23
+ emit_result(f"{args.move}: {desc}", json_mode=False)
24
+ return 0
25
+
26
+
27
+ def register(sub: argparse._SubParsersAction) -> None:
28
+ p = sub.add_parser("explain", help="Explain a devague move.")
29
+ p.add_argument("move", help="A move name (e.g. converge).")
30
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
31
+ p.set_defaults(func=cmd_explain)
@@ -0,0 +1,49 @@
1
+ """``devague export`` — write the buildable spec, only if the frame has converged."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ from devague import render, store
9
+ from devague.cli._errors import EXIT_USER_ERROR, DevagueError
10
+ from devague.cli._frames import resolve
11
+ from devague.cli._output import emit_result
12
+ from devague.convergence import evaluate
13
+
14
+ SPECS_DIR = Path("docs/specs")
15
+
16
+
17
+ def cmd_export(args: argparse.Namespace) -> int:
18
+ frame = resolve(args.frame)
19
+ result = evaluate(frame)
20
+ if not result.passed:
21
+ raise DevagueError(
22
+ EXIT_USER_ERROR,
23
+ "frame has not converged; cannot export",
24
+ "resolve: " + "; ".join(result.missing),
25
+ )
26
+ text = render.render(frame, args.format)
27
+ SPECS_DIR.mkdir(parents=True, exist_ok=True)
28
+ out_path = SPECS_DIR / f"{frame.slug}.md"
29
+ out_path.write_text(text, encoding="utf-8")
30
+ frame.status = "exported"
31
+ store.save(frame)
32
+ if getattr(args, "json", False):
33
+ emit_result({"path": str(out_path), "format": args.format}, json_mode=True)
34
+ else:
35
+ emit_result(f"exported spec to {out_path}", json_mode=False)
36
+ return 0
37
+
38
+
39
+ def register(sub: argparse._SubParsersAction) -> None:
40
+ p = sub.add_parser("export", help="Export the buildable spec (requires convergence).")
41
+ p.add_argument(
42
+ "--format",
43
+ default="spec-md",
44
+ choices=("spec-md",),
45
+ help="Renderer format (default: spec-md).",
46
+ )
47
+ p.add_argument("--frame", help="Frame slug (default: current).")
48
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
49
+ p.set_defaults(func=cmd_export)
@@ -0,0 +1,66 @@
1
+ """``devague interrogate`` — pressure-test a claim with honesty conditions / hard questions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import store
8
+ from devague.cli._errors import EXIT_USER_ERROR, DevagueError
9
+ from devague.cli._frames import resolve
10
+ from devague.cli._output import emit_result
11
+
12
+
13
+ def cmd_interrogate(args: argparse.Namespace) -> int:
14
+ frame = resolve(args.frame)
15
+ claim = frame.find_claim(args.claim_id)
16
+ if claim is None:
17
+ raise DevagueError(EXIT_USER_ERROR, f"no such claim: {args.claim_id}", "run 'devague show'")
18
+ added: list[dict] = []
19
+ if args.honesty:
20
+ h = frame.add_honesty(claim, args.honesty, origin=args.origin)
21
+ added.append({"kind": "honesty", "id": h.id, "status": h.status})
22
+ if args.risk:
23
+ q = frame.add_hard_question(claim, f"risk: {args.risk}", blocking=False)
24
+ added.append({"kind": "hard_question", "id": q.id, "status": "open"})
25
+ if args.hard_question:
26
+ q = frame.add_hard_question(claim, args.hard_question, blocking=args.blocking)
27
+ added.append(
28
+ {"kind": "hard_question", "id": q.id, "status": "blocking" if q.blocking else "open"}
29
+ )
30
+ if args.contradicts:
31
+ q = frame.add_hard_question(claim, f"contradiction with {args.contradicts}?", blocking=True)
32
+ added.append({"kind": "hard_question", "id": q.id, "status": "blocking"})
33
+ if not added:
34
+ raise DevagueError(
35
+ EXIT_USER_ERROR,
36
+ "nothing to interrogate",
37
+ "pass --honesty / --hard-question / --risk / --contradicts",
38
+ )
39
+ store.save(frame)
40
+ if getattr(args, "json", False):
41
+ emit_result({"claim": claim.id, "added": added}, json_mode=True)
42
+ else:
43
+ emit_result(
44
+ f"interrogated {claim.id}: " + ", ".join(f"{a['kind']} {a['id']}" for a in added),
45
+ json_mode=False,
46
+ )
47
+ return 0
48
+
49
+
50
+ def register(sub: argparse._SubParsersAction) -> None:
51
+ p = sub.add_parser("interrogate", help="Attach honesty conditions / hard questions to a claim.")
52
+ p.add_argument("claim_id", help="Claim id (e.g. c1).")
53
+ p.add_argument("--honesty", help="An honesty condition (what must be true).")
54
+ p.add_argument("--hard-question", dest="hard_question", help="A hard question.")
55
+ p.add_argument("--risk", help="A risk (recorded as a non-blocking hard question).")
56
+ p.add_argument("--contradicts", help="Claim id this contradicts (records a blocking question).")
57
+ p.add_argument("--blocking", action="store_true", help="Mark the hard question blocking.")
58
+ p.add_argument(
59
+ "--origin",
60
+ choices=("user", "llm"),
61
+ default="llm",
62
+ help="Who proposed the honesty condition.",
63
+ )
64
+ p.add_argument("--frame", help="Frame slug (default: current).")
65
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
66
+ p.set_defaults(func=cmd_interrogate)
@@ -0,0 +1,88 @@
1
+ """``devague learn`` — teach the working-backwards method and the moves."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import __version__
8
+ from devague.cli._output import emit_result
9
+
10
+ MOVES = {
11
+ "new": "Start a frame from the announcement (pretend it shipped).",
12
+ "capture": "Record and classify a claim (audience, after_state, boundary, ...).",
13
+ "interrogate": "Pressure-test a claim: honesty conditions, hard questions, contradictions.",
14
+ "confirm": "Confirm a claim or honesty condition (user-only — no fabricated rigor).",
15
+ "reject": "Reject a claim or honesty condition.",
16
+ "park": "Move uncertainty into first-class open vagueness instead of forcing an answer.",
17
+ "converge": "Check whether the frame is solid enough to export a spec.",
18
+ "export": "Write the buildable spec — only once the frame converges.",
19
+ "show": "Render the Announcement Frame.",
20
+ "list": "List frames.",
21
+ }
22
+
23
+ FIRST_QUESTION = "What's the announcement?"
24
+ SUPPORTING_PROMPT = (
25
+ "Pretend this shipped successfully. What would you announce to users, "
26
+ "teammates, or yourself?"
27
+ )
28
+
29
+ # The canonical guided sequence (devague#4). The engine is move-driven, not a
30
+ # rigid wizard — this is the recommended arc, with the move that advances each.
31
+ STAGES = [
32
+ ("Announcement", "what are we saying shipped?", "new"),
33
+ ("Audience", "who needs this?", "capture --kind audience"),
34
+ ("After", "what changed for them?", "capture --kind after_state"),
35
+ ("Matter", "why is it worth doing?", "capture --kind why_it_matters"),
36
+ ("Before", "what pain made this necessary?", "capture --kind before_state"),
37
+ ("Honest", "what must be true for the announcement to be honest?", "interrogate --honesty"),
38
+ ("FAQ", "what hard questions remain?", "interrogate --hard-question"),
39
+ ("Boundaries", "what are we not promising?", "capture --kind boundary"),
40
+ ("Success", "how will we know?", "capture --kind success_signal"),
41
+ ("Spec", "what should be built?", "converge -> export"),
42
+ ]
43
+
44
+ _TEXT = (
45
+ "devague turns a vague idea into a buildable spec by working backwards.\n\n"
46
+ f"First question: {FIRST_QUESTION}\n"
47
+ f" {SUPPORTING_PROMPT}\n\n"
48
+ "Start from that announcement, then build an Announcement Frame by capturing\n"
49
+ "claims, interrogating them, parking what's still vague, and converging.\n"
50
+ "The arc emerges from the moves; it is not a fixed wizard. You (the agent)\n"
51
+ "choose the next move; devague tracks state. LLM-proposed claims and honesty\n"
52
+ "conditions stay 'proposed' until the user confirms them.\n\n"
53
+ "Guided stages (the recommended sequence — drive them with the moves):\n"
54
+ + "\n".join(
55
+ f" {i:>2}. {name:<13} {prompt} [{move}]"
56
+ for i, (name, prompt, move) in enumerate(STAGES, 1)
57
+ )
58
+ + "\n\nMoves:\n"
59
+ + "\n".join(f" {name:<11} {desc}" for name, desc in MOVES.items())
60
+ )
61
+
62
+
63
+ def cmd_learn(args: argparse.Namespace) -> int:
64
+ if getattr(args, "json", False):
65
+ emit_result(
66
+ {
67
+ "tool": "devague",
68
+ "version": __version__,
69
+ "first_question": FIRST_QUESTION,
70
+ "supporting_prompt": SUPPORTING_PROMPT,
71
+ "stages": [
72
+ {"step": i, "name": name, "prompt": prompt, "move": move}
73
+ for i, (name, prompt, move) in enumerate(STAGES, 1)
74
+ ],
75
+ "moves": list(MOVES),
76
+ "summary": _TEXT,
77
+ },
78
+ json_mode=True,
79
+ )
80
+ else:
81
+ emit_result(_TEXT, json_mode=False)
82
+ return 0
83
+
84
+
85
+ def register(sub: argparse._SubParsersAction) -> None:
86
+ p = sub.add_parser("learn", help="Teach devague's working-backwards method.")
87
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
88
+ p.set_defaults(func=cmd_learn)
@@ -0,0 +1,27 @@
1
+ """``devague list`` — list frames and mark the current one."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import store
8
+ from devague.cli._output import emit_result
9
+
10
+
11
+ def cmd_list(args: argparse.Namespace) -> int:
12
+ slugs = store.list_slugs()
13
+ current = store.current_slug()
14
+ if getattr(args, "json", False):
15
+ emit_result({"frames": slugs, "current": current}, json_mode=True)
16
+ elif not slugs:
17
+ emit_result("no frames yet", json_mode=False)
18
+ else:
19
+ lines = [("* " if s == current else " ") + s for s in slugs]
20
+ emit_result("\n".join(lines), json_mode=False)
21
+ return 0
22
+
23
+
24
+ def register(sub: argparse._SubParsersAction) -> None:
25
+ p = sub.add_parser("list", help="List frames.")
26
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
27
+ p.set_defaults(func=cmd_list)
@@ -0,0 +1,33 @@
1
+ """``devague new`` — start a frame from an announcement (the first move)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import store
8
+ from devague.cli._output import emit_result
9
+ from devague.frame import Frame
10
+
11
+
12
+ def cmd_new(args: argparse.Namespace) -> int:
13
+ title = args.title or args.announcement
14
+ frame = Frame(slug=store.unique_slug(store.slugify(title)), title=title)
15
+ frame.add_claim("announcement", args.announcement, origin="user")
16
+ store.save(frame)
17
+ if getattr(args, "json", False):
18
+ emit_result({"slug": frame.slug, "title": title, "claims": 1}, json_mode=True)
19
+ else:
20
+ emit_result(f"created frame '{frame.slug}' (announcement = c1)", json_mode=False)
21
+ return 0
22
+
23
+
24
+ def register(sub: argparse._SubParsersAction) -> None:
25
+ p = sub.add_parser("new", help="Start a frame from an announcement.")
26
+ p.add_argument(
27
+ "announcement",
28
+ help="What's the announcement? Pretend this shipped successfully — what "
29
+ "would you announce to users, teammates, or yourself?",
30
+ )
31
+ p.add_argument("--title", help="Frame title (defaults to the announcement).")
32
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
33
+ p.set_defaults(func=cmd_new)
@@ -0,0 +1,31 @@
1
+ """``devague park`` — move uncertainty into first-class open vagueness."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import store
8
+ from devague.cli._frames import resolve
9
+ from devague.cli._output import emit_result
10
+ from devague.frame import VAGUENESS_KINDS
11
+
12
+
13
+ def cmd_park(args: argparse.Namespace) -> int:
14
+ frame = resolve(args.frame)
15
+ v = frame.add_vagueness(args.text, args.kind, claim_id=args.claim)
16
+ store.save(frame)
17
+ if getattr(args, "json", False):
18
+ emit_result({"id": v.id, "kind": v.kind}, json_mode=True)
19
+ else:
20
+ emit_result(f"parked {v.id} ({v.kind})", json_mode=False)
21
+ return 0
22
+
23
+
24
+ def register(sub: argparse._SubParsersAction) -> None:
25
+ p = sub.add_parser("park", help="Record open vagueness instead of forcing an answer.")
26
+ p.add_argument("text", help="The uncertainty.")
27
+ p.add_argument("--kind", required=True, choices=VAGUENESS_KINDS, help="Vagueness kind.")
28
+ p.add_argument("--claim", help="Link to a claim id.")
29
+ p.add_argument("--frame", help="Frame slug (default: current).")
30
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
31
+ p.set_defaults(func=cmd_park)
@@ -0,0 +1,19 @@
1
+ """``devague reject`` — reject a claim or honesty condition."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague.cli._commands.confirm import _transition
8
+
9
+
10
+ def cmd_reject(args: argparse.Namespace) -> int:
11
+ return _transition(args, "rejected")
12
+
13
+
14
+ def register(sub: argparse._SubParsersAction) -> None:
15
+ p = sub.add_parser("reject", help="Reject a claim or honesty condition.")
16
+ p.add_argument("id", help="Claim id (c*) or honesty id (h*).")
17
+ p.add_argument("--frame", help="Frame slug (default: current).")
18
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
19
+ p.set_defaults(func=cmd_reject)
@@ -0,0 +1,27 @@
1
+ """``devague show`` — render the current frame (markdown, or --json for raw state)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from devague import render
8
+ from devague.cli._frames import resolve
9
+ from devague.cli._output import emit_result
10
+ from devague.frame import to_dict
11
+
12
+
13
+ def cmd_show(args: argparse.Namespace) -> int:
14
+ frame = resolve(args.frame)
15
+ if getattr(args, "json", False):
16
+ emit_result(to_dict(frame), json_mode=True)
17
+ else:
18
+ emit_result(render.render(frame, args.format), json_mode=False)
19
+ return 0
20
+
21
+
22
+ def register(sub: argparse._SubParsersAction) -> None:
23
+ p = sub.add_parser("show", help="Render the current frame.")
24
+ p.add_argument("--format", default="frame-md", help="Renderer format (default: frame-md).")
25
+ p.add_argument("--frame", help="Frame slug (default: current).")
26
+ p.add_argument("--json", action="store_true", help="Emit the raw frame as JSON.")
27
+ p.set_defaults(func=cmd_show)