Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/onboarding/buddy_persona.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,31 @@
"later, so never call it a score, a result, or final.\n"
)

# Both halves are the point. The first is routing: "how do I create a project?" is
# a question about *this app*, and the corpus -- the project's own material -- has
# never heard of its pages, so a mentor that searches it either says nothing or
# describes a screen that does not exist. The second is the constraint that makes
# the answer safe: the guide is filtered for the reader, so a page it does not list
# is one their sidebar does not have, and sending them there is a dead end.
#
# Shared by both personas: a manager asks where things are at least as often as a
# hire does, and nothing in it presumes which of the two is reading.
_APP_GUIDE_CLAUSE = (
"- Questions about this app itself -- where a page or button is, how to do "
"something here (create a project, set up or choose a role, connect a source, "
"write arrival steps), what the page they are on is for -- are answered by "
"`get_app_guide`, never by `search_docs`: the docs describe the project the team "
"works on, not this app. Call it before answering, even when you think you know, "
'and read "this page" or "here" as the page it says they are looking at.\n'
"- Describe only the pages and buttons the guide names, in its words, and link a "
"page as a Markdown link to its path, like [Board](/board) -- or, where the guide "
"gives a how-to its own link, to that one, which opens the right tab. "
"Buttons on the app's own pages are not the chat buttons your tools make: naming "
"the ones the guide names is fine. "
"Never send somebody to a page the guide does not list as theirs to open: when it "
"says somebody else does it, say who to ask.\n"
)

# The onboarding-path clauses. The first is mounted on the *read* tool rather than on
# the actions: a mentor that knows the path exists should talk about it even where it
# may not touch it, and the backend mounts that read only for a hire who has a path.
Expand Down Expand Up @@ -536,6 +561,10 @@ def build_persona(
f"process works, and the hire-state tools ({rendered}) for the hire's "
"own progress.\n"
)
# Straight after the routing rule above, which it narrows: "how this product
# works" reads as including the app, and it does not.
if "get_app_guide" in available:
parts.append(_APP_GUIDE_CLAUSE)

# After the list above, which it narrows: that clause sorts tools by subject, and
# "what should I work on" belongs to no subject cleanly enough to be routed by it.
Expand Down Expand Up @@ -600,6 +629,8 @@ def _team_persona(available: set[str], capabilities_enabled: bool) -> str:
"`search_docs` for how the project itself works. A member id the tools "
"give you is how you name somebody; never guess one.\n"
)
if "get_app_guide" in available:
parts.append(_APP_GUIDE_CLAUSE)
parts.append(_TEAM_FACTS_CLAUSE)
if "open_area" in available:
parts.append(_TEAM_AREA_CLAUSE)
Expand Down
47 changes: 47 additions & 0 deletions tests/onboarding/test_buddy_persona.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,50 @@ def test_search_only_names_grep_only_when_it_is_mounted() -> None:
assert "`grep` for an exact name" in with_grep
assert "`grep` for an exact name" in team
assert "grep" not in without


def test_app_questions_are_routed_to_the_guide_not_the_corpus() -> None:
"""The corpus is the project's material; it has never heard of this app's pages."""
persona = build_persona([*_ALL_TOOLS, "get_app_guide"])

assert "`get_app_guide`, never by `search_docs`" in persona
assert "create a project" in persona


def test_the_guide_rule_forbids_sending_somebody_to_a_page_they_cannot_open() -> None:
persona = build_persona([*_ALL_TOOLS, "get_app_guide"])

assert "Never send somebody to a page the guide does not list" in persona
assert "say who to ask" in persona
# Root-relative, so the chat opens it in place rather than in a new tab. A page
# everybody can open: an example is a temptation, so it must never be a dead end.
assert "[Board](/board)" in persona
# A how-to's own link opens the tab the steps talk about.
assert "gives a how-to its own link, to that one" in persona


def test_the_guide_rule_frees_the_app_buttons_from_the_no_button_rule() -> None:
"""'Never describe a button' is about chat proposals, not the app's own pages."""
persona = build_persona([*_ALL_TOOLS, "get_app_guide"])

assert "Never describe a button instead of making one" in persona
assert "not the chat buttons your tools make" in persona


def test_the_guide_rule_follows_the_routing_rule_it_narrows() -> None:
"""'How this product works' reads as including the app; the next rule says not."""
persona = build_persona([*_ALL_TOOLS, "get_app_guide"])

routing = persona.index("hire-state tools")
assert routing < persona.index("`get_app_guide`, never by")


def test_the_guide_rule_is_absent_without_the_tool() -> None:
assert "get_app_guide" not in build_persona(_ALL_TOOLS)
assert "get_app_guide" not in build_persona(_TEAM_TOOLS, team_mode=True)


def test_a_manager_is_given_the_guide_rule_too() -> None:
persona = build_persona([*_TEAM_TOOLS, "get_app_guide"], team_mode=True)

assert "`get_app_guide`, never by `search_docs`" in persona
Loading