refactor(setup-instructions): unified layout with mandatory agnes init

Adds `_step_numbers(*, has_marketplace, has_skills)` so step numbering
lives in one place instead of being split across three branches in
`resolve_lines`. Pins the unified layout in the tests:

  No plugins:     1 install, 2 init, 3 catalog, 4 diagnose, 5 skills, 6 confirm
  With plugins:   1, 2, 3, 4 preflight, 5 marketplace, 6 diagnose, 7 skills, 8 confirm

`agnes auth import-token` / `agnes auth whoami` are now banned from the
rendered prompt — `agnes init` subsumes them. The renamed
`test_resolve_lines_no_plugins_unified_six_step_layout` asserts those
strings are absent and that the new step headers (`Bootstrap your Agnes
workspace`, `Verify the data is queryable`) are present.

Plan: docs/superpowers/plans/2026-05-04-unified-setup-prompt.md task 2.
This commit is contained in:
ZdenekSrotyr 2026-05-04 22:10:05 +02:00
parent 9334beed15
commit e16698c3cc
2 changed files with 66 additions and 20 deletions

View file

@ -635,6 +635,44 @@ def _preamble_lines(*, has_ca: bool) -> list[str]:
return lines return lines
def _step_numbers(*, has_marketplace: bool, has_skills: bool = True) -> dict[str, str]:
"""Compute the step numbers for the unified layout based on which optional
blocks are emitted.
Returns a dict keyed by logical step name; values are stringified
1-based step numbers (preserving the existing string-based helper API
so call sites stay diff-minimal).
Mandatory steps (always emitted): install (1), init (2), catalog (3),
diagnose, confirm. Optional: preflight + marketplace (gated on
has_marketplace), skills (gated on has_skills default True; the
Resolved-Question section in the plan settled on always-on, so the
parameter is here purely to keep the helper general for future use,
not to expose a real toggle).
Step-0 (TLS trust block) sits outside this numbering it is gated by
has_ca and has its own "0)" header rendered inside the trust block
helper.
"""
n = 4
preflight = marketplace = ""
if has_marketplace:
preflight = str(n); n += 1
marketplace = str(n); n += 1
diagnose = str(n); n += 1
skills = str(n) if has_skills else ""
if has_skills:
n += 1
confirm = str(n)
return {
"preflight": preflight,
"marketplace": marketplace,
"diagnose": diagnose,
"skills": skills,
"confirm": confirm,
}
def resolve_lines( def resolve_lines(
wheel_filename: str, wheel_filename: str,
*, *,
@ -683,15 +721,10 @@ def resolve_lines(
# Step layout. Marketplace (when emitted) goes BEFORE diagnose/skills, # Step layout. Marketplace (when emitted) goes BEFORE diagnose/skills,
# so the human-loop skills question is the last step before Confirm. # so the human-loop skills question is the last step before Confirm.
# Numbers shift between the no-marketplace layout (4 diagnose, 5 skills, # `_step_numbers` returns the renumbered step labels in one place — no
# 6 confirm) and the marketplace layout (4 preflight, 5 marketplace, # branch on every helper — so the layout is unambiguous and trivially
# 6 diagnose, 7 skills, 8 confirm). # extendable when a future step is added.
if has_marketplace: steps = _step_numbers(has_marketplace=has_marketplace, has_skills=True)
preflight_step, marketplace_step = "4", "5"
diagnose_step, skills_step, confirm_step = "6", "7", "8"
else:
preflight_step = marketplace_step = "" # unused; here just for symmetry
diagnose_step, skills_step, confirm_step = "4", "5", "6"
lines: list[str] = [] lines: list[str] = []
if has_ca: if has_ca:
@ -700,18 +733,18 @@ def resolve_lines(
lines.extend(_install_cli_lines(has_ca=has_ca)) # 1 lines.extend(_install_cli_lines(has_ca=has_ca)) # 1
lines.extend(_init_lines()) # 2, 3 lines.extend(_init_lines()) # 2, 3
if has_marketplace: if has_marketplace:
lines.extend(_git_check_block(preflight_step)) # 4 lines.extend(_git_check_block(steps["preflight"])) # 4
lines.extend(_marketplace_block( # 5 lines.extend(_marketplace_block( # 5
names, effective_self_signed, has_ca=has_ca, step_num=marketplace_step, names, effective_self_signed, has_ca=has_ca, step_num=steps["marketplace"],
)) ))
# Diagnose + skills come AFTER the marketplace block (or right after # Diagnose + skills come AFTER the marketplace block (or right after
# the catalog smoke verify if there's no marketplace step at all). # the catalog smoke verify if there's no marketplace step at all).
lines.extend(_diagnose_skills_lines( lines.extend(_diagnose_skills_lines(
diagnose_num=diagnose_step, skills_num=skills_step, diagnose_num=steps["diagnose"], skills_num=steps["skills"],
)) ))
lines.append("") lines.append("")
lines.extend(_finale_lines( lines.extend(_finale_lines(
confirm_step_num=confirm_step, confirm_step_num=steps["confirm"],
has_ca=has_ca, has_ca=has_ca,
has_marketplace=has_marketplace, has_marketplace=has_marketplace,
)) ))

View file

@ -47,16 +47,26 @@ def test_render_setup_instructions_wires_all_placeholders():
assert "T-123" in out assert "T-123" in out
def test_resolve_lines_no_plugins_keeps_six_step_layout(): def test_resolve_lines_no_plugins_unified_six_step_layout():
"""Backwards-compat: empty plugin list → original 6-step layout, Confirm = 6.""" """Unified no-plugin layout: 1 install, 2 init, 3 catalog, 4 diagnose,
5 skills, 6 confirm. No marketplace, no preflight."""
from app.web.setup_instructions import resolve_lines from app.web.setup_instructions import resolve_lines
joined = "\n".join(resolve_lines("agnes.whl")) joined = "\n".join(resolve_lines("agnes.whl"))
# Mandatory unified-flow steps.
assert "1) Install the CLI" in joined
assert "2) Bootstrap your Agnes workspace" in joined
assert "3) Verify the data is queryable:" in joined
assert "4) Run diagnostics:" in joined
assert "5) Skills" in joined
assert "6) Confirm:" in joined assert "6) Confirm:" in joined
# No stray Confirms at other positions.
assert "7) Confirm:" not in joined assert "7) Confirm:" not in joined
assert "8) Confirm:" not in joined assert "8) Confirm:" not in joined
assert "claude plugin marketplace add" not in joined assert "claude plugin marketplace add" not in joined
assert "claude plugin install" not in joined assert "claude plugin install" not in joined
# No preflight step when there's no marketplace block to gate.
assert "Make sure git is installed" not in joined
# Legacy `git config sslVerify=false` downgrade must NOT be emitted. # Legacy `git config sslVerify=false` downgrade must NOT be emitted.
# Match the specific config line, not the bare substring (which appears # Match the specific config line, not the bare substring (which appears
# in the preamble as a "don't do this" example). # in the preamble as a "don't do this" example).
@ -69,6 +79,9 @@ def test_resolve_lines_no_plugins_keeps_six_step_layout():
assert "step 0(d)" not in joined assert "step 0(d)" not in joined
assert "Which CA bundle source got picked" not in joined assert "Which CA bundle source got picked" not in joined
assert "Whether the marketplace add went via direct HTTPS" not in joined assert "Whether the marketplace add went via direct HTTPS" not in joined
# Legacy admin-only auth verbs are gone — `agnes init` subsumes them.
assert "agnes auth import-token" not in joined
assert "agnes auth whoami" not in joined
def test_preamble_step_zero_d_reference_only_when_trust_block_emitted(): def test_preamble_step_zero_d_reference_only_when_trust_block_emitted():
@ -267,10 +280,10 @@ def test_trust_block_step_0c_does_not_reference_stale_step_number():
def test_resolve_lines_with_plugins_uses_install_first_diagnose_last_layout(): def test_resolve_lines_with_plugins_uses_install_first_diagnose_last_layout():
"""Marketplace layout puts install/login/git/marketplace BEFORE diagnose """Marketplace layout puts install/init/catalog/preflight/marketplace
and skills, so the human-loop skills question is the final blocking BEFORE diagnose and skills, so the human-loop skills question is the
step before Confirm. Step numbers: 4 git, 5 marketplace, 6 diagnose, final blocking step before Confirm. Step numbers: 4 preflight,
7 skills, 8 confirm.""" 5 marketplace, 6 diagnose, 7 skills, 8 confirm."""
from app.web.setup_instructions import resolve_lines from app.web.setup_instructions import resolve_lines
lines = resolve_lines( lines = resolve_lines(