Skip to content

Commit 629c98b

Browse files
committed
fix(smokes): restore full-public contract parity
1 parent 5fcdeaa commit 629c98b

10 files changed

Lines changed: 250 additions & 209 deletions

examples/bootstrap-command-pack-smoke.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -700,14 +700,14 @@ def test_skill_slash_fallback_contract() -> None:
700700
assert "do not route it to `loopx-pr-merge` unless" in normalized
701701
assert "loopx --format json pr-review --state all" not in skill_text
702702
assert "loopx --format json pr-review --state all" in pr_review_skill_text
703-
assert "full JSON first" in pr_review_normalized
703+
assert "Save the full first JSON packet before printing a compact projection" in pr_review_normalized
704704
assert "agent_response_contract" in pr_review_skill_text
705705
assert "pull_requests[].review_template" in pr_review_skill_text
706706
assert "pull_requests[].evidence_commands" in pr_review_skill_text
707-
assert "`.summary`, `.review_sequence`, or a table" in pr_review_skill_text
707+
assert "Do not pipe the only copy through `jq`" in pr_review_skill_text
708708
assert "review_groups.unmerged" in pr_review_skill_text
709709
assert "review_groups.merged" in pr_review_skill_text
710-
assert "Do not fill the five-block review from title" in pr_review_normalized
710+
assert "The five sections are output structure, while the execution contract is the evidence authority" in pr_review_normalized
711711

712712

713713
def main() -> int:

examples/cli-command-module-size-ownership-command-modularization-smoke.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"starter_visible_pilot.py": 340,
2626
}
2727
STARTER_COMMAND_OWNERS = {
28+
"start-goal": "start_goal.py",
2829
"new-project-prompt": "starter_bootstrap_registration.py",
2930
"codex-cli-bootstrap-message": "starter_bootstrap_registration.py",
3031
"codex-cli-tui-bootstrap-smoke-bundle": "starter_bootstrap_registration.py",

examples/cli-starter-bootstrap-family-command-modularization-smoke.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
STARTER = ROOT / "loopx" / "cli_commands" / "starter.py"
1212
BOOTSTRAP = ROOT / "loopx" / "cli_commands" / "starter_bootstrap.py"
1313
BOOTSTRAP_REGISTRATION = ROOT / "loopx" / "cli_commands" / "starter_bootstrap_registration.py"
14+
START_GOAL = ROOT / "loopx" / "cli_commands" / "start_goal.py"
1415
INIT = ROOT / "loopx" / "cli_commands" / "__init__.py"
1516

1617

@@ -49,6 +50,7 @@ def assert_source_shape() -> None:
4950
starter_source = STARTER.read_text(encoding="utf-8")
5051
bootstrap_source = BOOTSTRAP.read_text(encoding="utf-8")
5152
registration_source = BOOTSTRAP_REGISTRATION.read_text(encoding="utf-8")
53+
start_goal_source = START_GOAL.read_text(encoding="utf-8")
5254
init_source = INIT.read_text(encoding="utf-8")
5355

5456
forbidden_starter_markers = [
@@ -88,14 +90,22 @@ def assert_source_shape() -> None:
8890
"def register_starter_bootstrap_commands(",
8991
'subparsers.add_parser(\n "agent-onboard"',
9092
'subparsers.add_parser(\n "bootstrap-command-pack"',
91-
'subparsers.add_parser(\n "start-goal"',
9293
'subparsers.add_parser(\n "new-project-prompt"',
9394
):
9495
require(
9596
marker in registration_source,
9697
f"starter_bootstrap_registration.py missing marker: {marker}",
9798
)
9899

100+
for marker in (
101+
"def register_start_goal_command(",
102+
'subparsers.add_parser(\n "start-goal"',
103+
):
104+
require(
105+
marker in start_goal_source,
106+
f"start_goal.py missing marker: {marker}",
107+
)
108+
99109
for marker in (
100110
"handle_starter_bootstrap_command",
101111
"register_starter_bootstrap_commands",

examples/install-local-smoke.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ def main() -> int:
379379
"never infer `verified` from title",
380380
"formal `REQUEST_CHANGES`",
381381
"Read the published review back",
382-
"approval still routes through `loopx-pr-merge`",
382+
"Merge still routes through `loopx-pr-merge`",
383383
):
384384
assert phrase in pr_review_text, phrase
385385
assert "Do not use this skill to approve" not in pr_review_text, pr_review_text
@@ -771,7 +771,7 @@ def main() -> int:
771771
)
772772
assert "`LOOPX_TURN=<current_time_iso>`; reuse." in payload["task_body"], payload
773773
assert "guard receipt; 2 stalls->replan" in payload["task_body"], payload
774-
assert "actual class/scale/outcome accountable refresh->spend" in payload["task_body"], payload
774+
assert "no-change=`surface_only`/no spend" in payload["task_body"], payload
775775
assert payload["cli_bin"] == "loopx", payload
776776

777777
canary_cli = subprocess.run(

loopx/cli_commands/_host_thread.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import os
5+
6+
7+
def current_host_thread_id(args: argparse.Namespace) -> str | None:
8+
explicit = getattr(args, "thread_id", None)
9+
if explicit:
10+
return str(explicit)
11+
if getattr(args, "host_surface", None) == "codex-app":
12+
return os.environ.get("CODEX_THREAD_ID") or None
13+
return None

loopx/cli_commands/start_goal.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import re
5+
from collections.abc import Callable
6+
from pathlib import Path
7+
8+
from ..bootstrap_command_pack import (
9+
START_GOAL_CAPABILITY_ROUTES,
10+
START_GOAL_HOST_SURFACES,
11+
build_start_goal_guided_packet,
12+
build_start_goal_host_surface_selection_packet,
13+
render_start_goal_guided_markdown,
14+
)
15+
from ._host_thread import current_host_thread_id
16+
17+
PrintPayload = Callable[
18+
[dict[str, object], str, Callable[[dict[str, object]], str]],
19+
None,
20+
]
21+
22+
_CAPABILITY_ROUTE_SWITCH = "--capability-route"
23+
_CAPABILITY_ROUTE_PREFIX = re.compile(
24+
r"\A--capability-route(?:=(?P<equals>\S+)|\s+(?P<spaced>\S+))"
25+
r"(?P<remainder>[\s\S]*)\Z"
26+
)
27+
28+
29+
def add_capability_route_argument(parser: argparse.ArgumentParser) -> None:
30+
parser.add_argument(
31+
"--capability-route",
32+
choices=START_GOAL_CAPABILITY_ROUTES,
33+
help=(
34+
"Explicit product capability route for this goal start. Goal text never "
35+
"selects a capability route."
36+
),
37+
)
38+
39+
40+
def register_start_goal_command(subparsers: argparse._SubParsersAction) -> None:
41+
start_goal_parser = subparsers.add_parser(
42+
"start-goal",
43+
help="Preview a guided /loopx <goal text> start transaction without mutating state.",
44+
)
45+
start_goal_parser.add_argument(
46+
"--guided",
47+
action="store_true",
48+
help="Required for now: render the guided dry-run transaction packet.",
49+
)
50+
start_goal_parser.add_argument("--project", default=".", help="Project directory to inspect.")
51+
start_goal_parser.add_argument("--goal-id", help="Goal id. Defaults to <project-name>-goal.")
52+
start_goal_parser.add_argument(
53+
"--agent-id",
54+
help=(
55+
"Explicit registered LoopX identity for an ongoing session or exact "
56+
"user-requested takeover. When omitted, a bound thread identity is reused "
57+
"when available; otherwise new onboarding defaults to fresh registration."
58+
),
59+
)
60+
start_goal_parser.add_argument(
61+
"--thread-id",
62+
help=(
63+
"Stable opaque host thread id used to reuse the bound agent lane. "
64+
"Codex App defaults to the ambient CODEX_THREAD_ID when available."
65+
),
66+
)
67+
start_goal_parser.add_argument(
68+
"--new-peer",
69+
action="store_true",
70+
help="Explicitly request a fresh agent identity for this host thread.",
71+
)
72+
start_goal_parser.add_argument(
73+
"--cli-bin",
74+
default="loopx",
75+
help="LoopX CLI binary name embedded in generated commands.",
76+
)
77+
start_goal_parser.add_argument(
78+
"--host-surface",
79+
choices=START_GOAL_HOST_SURFACES,
80+
help=(
81+
"Exact host surface that will own loop activation after todo writeback. "
82+
"When omitted, start-goal returns a read-only host selection gate."
83+
),
84+
)
85+
start_goal_parser.add_argument(
86+
"--available-capability",
87+
dest="available_capabilities",
88+
action="append",
89+
help="Capability available in this host loop. Repeat for multiple capabilities.",
90+
)
91+
add_capability_route_argument(start_goal_parser)
92+
goal_input_group = start_goal_parser.add_mutually_exclusive_group(required=True)
93+
goal_input_group.add_argument(
94+
"--goal-text",
95+
help="Exact goal text to plan before todo writeback.",
96+
)
97+
goal_input_group.add_argument(
98+
"--slash-command-arguments",
99+
help=(
100+
"Complete visible /loopx arguments. The CLI consumes only an optional "
101+
"leading --capability-route switch and treats the remainder as goal text. "
102+
"Use --slash-command-arguments='<arguments>' when the value begins with --."
103+
),
104+
)
105+
start_goal_parser.add_argument(
106+
"--include-command-pack-detail",
107+
action="store_true",
108+
help=(
109+
"Include the complete nested bootstrap command pack. The default guided "
110+
"projection keeps the actionable transaction and advertises this cold path."
111+
),
112+
)
113+
114+
115+
def _resolve_start_goal_input(args: argparse.Namespace) -> tuple[str, str | None]:
116+
raw_arguments = getattr(args, "slash_command_arguments", None)
117+
capability_route = getattr(args, "capability_route", None)
118+
if raw_arguments is None:
119+
return str(args.goal_text), capability_route
120+
if capability_route is not None:
121+
raise ValueError(
122+
"--slash-command-arguments cannot be combined with --capability-route; "
123+
"the raw argument string already owns the optional route switch"
124+
)
125+
126+
normalized = str(raw_arguments).strip()
127+
if not normalized:
128+
raise ValueError("--slash-command-arguments must contain goal text")
129+
if not normalized.startswith(_CAPABILITY_ROUTE_SWITCH):
130+
return normalized, None
131+
132+
match = _CAPABILITY_ROUTE_PREFIX.fullmatch(normalized)
133+
if match is None:
134+
raise ValueError(
135+
"malformed leading --capability-route in --slash-command-arguments"
136+
)
137+
route = str(match.group("equals") or match.group("spaced") or "")
138+
if route not in START_GOAL_CAPABILITY_ROUTES:
139+
raise ValueError(
140+
"unsupported --capability-route; expected one of: "
141+
+ ", ".join(START_GOAL_CAPABILITY_ROUTES)
142+
)
143+
goal_text = str(match.group("remainder") or "").strip()
144+
if not goal_text:
145+
raise ValueError(
146+
"--slash-command-arguments must contain goal text after --capability-route"
147+
)
148+
return goal_text, route
149+
150+
151+
def handle_start_goal_command(
152+
args: argparse.Namespace,
153+
print_payload: PrintPayload,
154+
) -> int:
155+
if not bool(getattr(args, "guided", False)):
156+
payload = {
157+
"ok": False,
158+
"schema_version": "loopx_start_goal_guided_v0",
159+
"error": "`loopx start-goal` currently requires --guided",
160+
"suggested_command": "loopx start-goal --guided --goal-text '<goal text>'",
161+
}
162+
print_payload(payload, args.format, render_start_goal_guided_markdown)
163+
return 2
164+
try:
165+
goal_text, capability_route = _resolve_start_goal_input(args)
166+
except ValueError as exc:
167+
payload = {
168+
"ok": False,
169+
"schema_version": "loopx_start_goal_guided_v0",
170+
"error": str(exc),
171+
"suggested_command": (
172+
"loopx start-goal --guided "
173+
"--slash-command-arguments='<exact /loopx arguments>'"
174+
),
175+
}
176+
print_payload(payload, args.format, render_start_goal_guided_markdown)
177+
return 2
178+
if not args.host_surface:
179+
payload = build_start_goal_host_surface_selection_packet(
180+
project=Path(args.project),
181+
goal_id=args.goal_id,
182+
agent_id=args.agent_id,
183+
thread_id=current_host_thread_id(args),
184+
new_peer=bool(getattr(args, "new_peer", False)),
185+
cli_bin=args.cli_bin,
186+
goal_text=goal_text,
187+
available_capabilities=args.available_capabilities,
188+
capability_route=capability_route,
189+
include_command_pack_detail=bool(args.include_command_pack_detail),
190+
)
191+
print_payload(payload, args.format, render_start_goal_guided_markdown)
192+
return 0
193+
payload = build_start_goal_guided_packet(
194+
project=Path(args.project),
195+
goal_id=args.goal_id,
196+
agent_id=args.agent_id,
197+
thread_id=current_host_thread_id(args),
198+
new_peer=bool(getattr(args, "new_peer", False)),
199+
cli_bin=args.cli_bin,
200+
host_surface=args.host_surface,
201+
goal_text=goal_text,
202+
available_capabilities=args.available_capabilities,
203+
capability_route=capability_route,
204+
include_command_pack_detail=bool(args.include_command_pack_detail),
205+
)
206+
print_payload(payload, args.format, render_start_goal_guided_markdown)
207+
return 0

0 commit comments

Comments
 (0)