intent.py file-level

at sha256:6 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:c docs(KD-STAGING): sync governance after KD-6b DONE Mirror workspace go… · aaronrene · Jul 10, 2026
1 """``muse coord intent`` β€” declare a specific operation before executing it.
2
3 Records a structured intent extending an existing reservation. Whereas
4 ``muse reserve`` says "I will touch these symbols", ``muse coord intent``
5 says "I will rename src/billing.py::compute_total to compute_invoice_total".
6
7 This additional detail enables:
8
9 * ``muse forecast`` to compute more precise conflict predictions
10 (a rename conflicts differently with a delete than a modify does).
11 * ``muse plan-merge`` to classify conflicts by a semantic taxonomy.
12 * Audit trail of what each agent intended before committing.
13
14 Usage::
15
16 muse coord intent "src/billing.py::compute_total" \\
17 --op rename --detail "rename to compute_invoice_total" \\
18 --reservation-id <UUID>
19
20 muse coord intent "src/auth.py::validate_token" \\
21 --op extract --detail "extract into src/auth/validators.py" \\
22 --run-id agent-42
23
24 muse coord intent "src/core.py::hash_content" --op delete --run-id refactor-bot
25
26 JSON output schema::
27
28 {
29 "schema_version": str,
30 "intent_id": str, // UUID
31 "reservation_id": str, // UUID or "" if standalone
32 "run_id": str,
33 "branch": str,
34 "addresses": [str, ...],
35 "operation": str,
36 "created_at": str, // ISO 8601
37 "detail": str
38 }
39
40 Exit codes::
41
42 0 β€” intent recorded successfully
43 1 β€” bad arguments (unknown --op, --run-id too long, --detail too long,
44 too many addresses, --reservation-id not a valid UUID)
45
46 Flags:
47
48 ``--op OPERATION``
49 Required. The operation being declared:
50 delete | extract | inline | merge | modify | move | rename | split.
51
52 ``--detail TEXT``
53 Human-readable description of the intended change.
54 Maximum length: 4096 characters.
55
56 ``--reservation-id UUID``
57 Link to an existing reservation (optional; creates a standalone intent
58 if omitted). When provided, must be a valid UUID.
59
60 ``--run-id ID``
61 Agent identifier (used when --reservation-id is not provided).
62 Maximum length: 256 characters.
63
64 ``--json`` / ``--format json``
65 Emit intent details as compact JSON on stdout.
66 """
67
68 from __future__ import annotations
69
70 import argparse
71 import json
72 import logging
73 import sys
74
75 from muse.core.coordination import _validate_reservation_id, create_intent
76 from muse.core.errors import ExitCode
77 from muse.core.repo import require_repo
78 from muse.core.store import read_current_branch
79 from muse.core.validation import sanitize_display
80
81 logger = logging.getLogger(__name__)
82
83 # ── Input constraints ─────────────────────────────────────────────────────────
84
85 #: Allowed values for ``--op``. Intent supports more granular operations
86 #: than ``reserve`` (which only tracks which symbols will change).
87 _VALID_OPS: frozenset[str] = frozenset({
88 "rename", "move", "modify", "extract", "delete", "inline", "split", "merge",
89 })
90
91 #: Maximum byte-length of the ``--run-id`` value. Mirrors all other
92 #: coordination commands so audit records stay bounded in size.
93 _MAX_RUN_ID_LEN: int = 256
94
95 #: Maximum character-length of the ``--detail`` value. Bounds intent file
96 #: sizes and prevents pathological memory use when loading large intent sets.
97 _MAX_DETAIL_LEN: int = 4096
98
99 #: Maximum number of symbol addresses per intent call. Mirrors reserve.py.
100 _MAX_ADDRESSES: int = 1000
101
102
103 # ── CLI registration ──────────────────────────────────────────────────────────
104
105
106 def register(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
107 """Register the ``intent`` subcommand on *subparsers* (under ``muse coord``).
108
109 Wires all flags with their defaults, choices, and help text so that
110 ``--help`` output is accurate. Sets ``func`` to :func:`run`.
111 """
112 parser = subparsers.add_parser(
113 "intent",
114 help="Declare a specific operation before executing it.",
115 description=__doc__,
116 formatter_class=argparse.RawDescriptionHelpFormatter,
117 )
118 parser.add_argument(
119 "addresses",
120 nargs="+",
121 metavar="ADDRESS",
122 help=(
123 "Symbol address(es) this intent applies to. "
124 f"Maximum {_MAX_ADDRESSES} per call."
125 ),
126 )
127 parser.add_argument(
128 "--op",
129 dest="operation",
130 required=True,
131 choices=sorted(_VALID_OPS),
132 metavar="OPERATION",
133 help=(
134 "Operation to declare β€” one of: "
135 + ", ".join(sorted(_VALID_OPS))
136 + "."
137 ),
138 )
139 parser.add_argument(
140 "--detail",
141 default="",
142 metavar="TEXT",
143 help=(
144 "Human-readable description of the intended change. "
145 f"Maximum {_MAX_DETAIL_LEN} characters."
146 ),
147 )
148 parser.add_argument(
149 "--reservation-id",
150 dest="reservation_id",
151 default="",
152 metavar="UUID",
153 help=(
154 "Link to an existing reservation. When provided, must be a valid "
155 "UUID. Omit to create a standalone intent."
156 ),
157 )
158 parser.add_argument(
159 "--run-id",
160 dest="run_id",
161 default="unknown",
162 metavar="ID",
163 help=(
164 "Agent identifier (used when --reservation-id is not provided). "
165 f"Maximum {_MAX_RUN_ID_LEN} characters."
166 ),
167 )
168 parser.add_argument(
169 "--format", "-f",
170 default="text",
171 dest="fmt",
172 choices=("text", "json"),
173 help="Output format: text (default) or json.",
174 )
175 parser.add_argument(
176 "--json",
177 action="store_const",
178 const="json",
179 dest="fmt",
180 help="Shorthand for --format json.",
181 )
182 parser.set_defaults(func=run)
183
184
185 # ── Command implementation ────────────────────────────────────────────────────
186
187
188 def run(args: argparse.Namespace) -> None:
189 """Declare a specific operation before executing it.
190
191 Execution order
192 ---------------
193 1. **Validate inputs** β€” ``--run-id`` length, ``--detail`` length, address
194 count, and ``--reservation-id`` UUID syntax (when non-empty). All
195 validation fires before any file I/O; any failure exits
196 :attr:`~muse.core.errors.ExitCode.USER_ERROR` (1) with a message to
197 *stderr*. ``--op`` is validated by argparse ``choices`` before this
198 function is called.
199 2. **Resolve repo** β€” calls :func:`~muse.core.repo.require_repo` and reads
200 the current branch from the ref store.
201 3. **Write intent** β€” calls :func:`~muse.core.coordination.create_intent`,
202 which atomically writes ``.muse/coordination/intents/<uuid>.json``.
203 4. **Emit output** β€” compact JSON to *stdout* when ``--format json``,
204 or human-readable text otherwise.
205
206 What intents enable
207 -------------------
208 ``muse coord intent`` extends a reservation with operational detail. The
209 operation type enables ``muse forecast`` to compute more precise conflict
210 predictions β€” a rename conflicts differently from a delete. Intents are
211 write-once audit records and never affect VCS correctness.
212
213 Security
214 --------
215 * ``--run-id`` is capped at :data:`_MAX_RUN_ID_LEN` to bound audit record
216 size.
217 * ``--detail`` is capped at :data:`_MAX_DETAIL_LEN` to bound intent file
218 sizes and memory use when loading large intent sets.
219 * Address count is capped at :data:`_MAX_ADDRESSES`.
220 * ``--reservation-id``, when non-empty, is validated as a UUID before any
221 path construction (even though intent files reference, not construct,
222 the reservation path).
223 * All display strings in text output are passed through
224 :func:`~muse.core.validation.sanitize_display`.
225
226 Args:
227 args: Parsed ``argparse.Namespace`` with attributes ``addresses``,
228 ``operation``, ``detail``, ``reservation_id``, ``run_id``,
229 and ``fmt``.
230
231 Exit codes:
232 0 β€” intent recorded successfully.
233 1 β€” bad arguments.
234 """
235 addresses: list[str] = args.addresses
236 operation: str = args.operation
237 detail: str = args.detail
238 reservation_id: str = args.reservation_id
239 run_id: str = args.run_id
240 as_json: bool = args.fmt == "json"
241
242 # ── Input validation (before any file I/O) ────────────────────────────────
243
244 if len(run_id) > _MAX_RUN_ID_LEN:
245 msg = f"--run-id is too long ({len(run_id)} chars; max {_MAX_RUN_ID_LEN})"
246 if as_json:
247 print(json.dumps({"error": msg, "status": "bad_args"}))
248 else:
249 print(f"❌ {msg}", file=sys.stderr)
250 raise SystemExit(ExitCode.USER_ERROR)
251
252 if len(detail) > _MAX_DETAIL_LEN:
253 msg = f"--detail is too long ({len(detail)} chars; max {_MAX_DETAIL_LEN})"
254 if as_json:
255 print(json.dumps({"error": msg, "status": "bad_args"}))
256 else:
257 print(f"❌ {msg}", file=sys.stderr)
258 raise SystemExit(ExitCode.USER_ERROR)
259
260 if len(addresses) > _MAX_ADDRESSES:
261 msg = f"Too many addresses: {len(addresses)} (max {_MAX_ADDRESSES})"
262 if as_json:
263 print(json.dumps({"error": msg, "status": "bad_args"}))
264 else:
265 print(f"❌ {msg}", file=sys.stderr)
266 raise SystemExit(ExitCode.USER_ERROR)
267
268 if reservation_id:
269 try:
270 _validate_reservation_id(reservation_id)
271 except ValueError as exc:
272 msg = str(exc)
273 if as_json:
274 print(json.dumps({"error": msg, "status": "bad_reservation_id"}))
275 else:
276 print(f"❌ --reservation-id: {msg}", file=sys.stderr)
277 raise SystemExit(ExitCode.USER_ERROR)
278
279 root = require_repo()
280 branch = read_current_branch(root)
281
282 intent_record = create_intent(
283 root=root,
284 reservation_id=reservation_id,
285 run_id=run_id,
286 branch=branch,
287 addresses=addresses,
288 operation=operation,
289 detail=detail,
290 )
291
292 if as_json:
293 print(json.dumps(intent_record.to_dict()))
294 return
295
296 safe_intent_id = sanitize_display(intent_record.intent_id)
297 safe_run_id = sanitize_display(intent_record.run_id)
298 print(
299 f"\nβœ… Intent recorded\n"
300 f" Intent ID: {safe_intent_id}\n"
301 f" Operation: {operation}\n"
302 f" Addresses: {len(addresses)}\n"
303 f" Run ID: {safe_run_id}"
304 )
305 if detail:
306 print(f" Detail: {sanitize_display(detail)}")
307 if reservation_id:
308 print(f" Reservation: {sanitize_display(reservation_id)}")
309 print("\nRun 'muse forecast' to check for predicted conflicts.")