Skip to content

feat(p1): visible next-frontier per session (acceptance #19) + audit refresh#31

Merged
SolshineCode merged 1 commit into
mainfrom
feat/p1-next-frontier-and-audit-refresh
May 8, 2026
Merged

feat(p1): visible next-frontier per session (acceptance #19) + audit refresh#31
SolshineCode merged 1 commit into
mainfrom
feat/p1-next-frontier-and-audit-refresh

Conversation

@SolshineCode
Copy link
Copy Markdown
Owner

Closes audit P1 acceptance #19. TUI KingdomView always shows 'Next: ' at the bottom — a one-line medieval-voice prompt giving the player a reason to return. 16 new tests; 250 total. Audit doc header refreshed with closure status (every P0 and P1 from the original audit is now closed). Refs PR #22, kanban t_26404be3.

…doc closure header

Closes audit acceptance #19: 'Every play session ends with at least
one visible unresolved next-step frontier.' The TUI now appends a
'Next: <frontier>' line to KingdomView so the player always has a
reason to come back.

bridge/frontier.py:
- next_frontier(state) — pure function returning a single short
  medieval-voice string. Priority order: redirected (acute) > waiting
  (input expected) > inactive (registered but quiet) > active near
  momentum-tier-up > active general > all-suspended invite. Empty
  state gets a 'found your first city' onboarding nudge.
- Always non-empty so acceptance #19's 'at least one visible'
  guarantee is enforced by construction.
- frontier_priority_kind(state) — stable identifier for telemetry
  + tests; mirrors the branch tree of next_frontier.

tui/app.py — KingdomView.render_state appends a 'Next: ...' line at
the bottom on every refresh.

bridge/tests/test_frontier.py — 16 tests:
- Empty state → 'first city' frontier; all-suspended → chronicles
- Priority: redirected > active; waiting > inactive; inactive > active
- Active near tier-up calls out exact gap (e.g. '2 from')
- Active far from tier uses generic message
- Active at exact tier (gap=10) uses generic
- Highest-momentum active session is chosen as the frontier
- Every SessionStatus value yields a non-empty string
  (parametrized over SessionStatus)
- Session name appears in output

docs/audit-2026-05-08.md — header update: every P0 and P1 line item
identified in the audit is now closed (PR refs listed). Original
grid preserved as the historical baseline so the journey is
auditable.

Total: 250 passing (was 234; adds 16).

Closes audit P1: acceptance #19. Refs: PR #22, kanban t_26404be3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@SolshineCode SolshineCode merged commit 80a77d7 into main May 8, 2026
@SolshineCode SolshineCode deleted the feat/p1-next-frontier-and-audit-refresh branch May 8, 2026 21:26
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the 'next-frontier' suggestion system to satisfy PRD acceptance criterion #19, ensuring players always have a clear next step. The implementation includes a new prioritization logic for session states, medieval-themed string generation, and TUI integration. Feedback includes removing unused imports in bridge/frontier.py, refactoring duplicated logic between next_frontier and frontier_priority_kind into a shared helper to reduce maintenance overhead, and updating the audit documentation to explicitly reference this PR's contribution to the project's goals.

Comment thread bridge/frontier.py
Comment on lines +18 to +20
from typing import Optional

from bridge.session_state import Session, SessionState, SessionStatus
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The imports Optional and Session are unused in this file. Removing them keeps the code clean and adheres to standard Python practices.

Suggested change
from typing import Optional
from bridge.session_state import Session, SessionState, SessionStatus
from bridge.session_state import SessionState, SessionStatus

Comment thread bridge/frontier.py
Comment on lines +23 to +89
def next_frontier(state: SessionState) -> str:
"""Return one short medieval-voice 'come back to do X' string."""
sessions = state.sessions

if not sessions:
return "Found your first city — open a Claude Code session and the realm begins."

# Priority order: redirected (acute, transient) > waiting (input expected)
# > inactive (registered but quiet) > momentum-buildup > momentum-tier-up
# > goal-pending > generic come-back.

# 1) Any redirected session — surface that first; momentum is on the line.
redirected = [s for s in sessions if s.status == SessionStatus.REDIRECTED]
if redirected:
s = redirected[0]
return f"'{s.name}' is redirected — return to rebuild momentum."

# 2) Waiting sessions — the agent expects input, this is the warmest re-entry.
waiting = [s for s in sessions if s.status == SessionStatus.WAITING]
if waiting:
s = waiting[0]
return f"'{s.name}' awaits your council. The next charter starts with you."

# 3) Inactive sessions — registered but never engaged.
inactive = [s for s in sessions if s.status == SessionStatus.INACTIVE]
if inactive:
s = inactive[0]
return f"'{s.name}' is registered but quiet — open the session to begin."

# 4) Active session about to tier up its momentum (next +1 culture).
actives = [s for s in sessions if s.status == SessionStatus.ACTIVE]
if actives:
s = max(actives, key=lambda x: x.momentum)
next_tier = ((s.momentum // 10) + 1) * 10
gap = next_tier - s.momentum
if 0 < gap <= 3:
return f"'{s.name}' is {gap} from the next culture tier."
# 5) Generic come-back for active sessions.
return f"'{s.name}' is at work; return to claim the next charter."

# 6) All suspended (game-closed) — give a return invite.
return "The chronicles preserve your work. Return when you're ready."


def frontier_priority_kind(state: SessionState) -> str:
"""Return a stable identifier for which frontier kind fired.

Useful for testing + telemetry. Mirrors next_frontier's branches.
"""
sessions = state.sessions
if not sessions:
return "first_city"
if any(s.status == SessionStatus.REDIRECTED for s in sessions):
return "redirected"
if any(s.status == SessionStatus.WAITING for s in sessions):
return "waiting"
if any(s.status == SessionStatus.INACTIVE for s in sessions):
return "inactive"
actives = [s for s in sessions if s.status == SessionStatus.ACTIVE]
if actives:
s = max(actives, key=lambda x: x.momentum)
next_tier = ((s.momentum // 10) + 1) * 10
gap = next_tier - s.momentum
if 0 < gap <= 3:
return "momentum_tier_up_imminent"
return "active_general"
return "all_suspended"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is significant logic duplication between next_frontier and frontier_priority_kind, particularly in the session filtering and the momentum tier calculation for active sessions. This increases maintenance overhead and the risk of logic drift if the priority rules or tier thresholds change.

Consider refactoring the shared logic into a private helper function (e.g., _get_frontier_info) that returns the relevant session and a category identifier. This would also allow for a more efficient implementation that avoids multiple list comprehensions over the sessions list by using a single-pass search.

Comment thread docs/audit-2026-05-08.md
Comment on lines +8 to +12
> **Closure update — every P0 and P1 line item identified in this audit
> is now closed.** PR refs: #23 (P0 #1 enum), #24 (P0 #3 Stop→COMPLETED),
> #25 (P0 #2 schema), #26 (BR-9), #27 (#11 + #12), #28 (GM-8), #29
> (GM-5), #30 (#9 + #20). The original audit grid below is preserved
> as the historical baseline.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The 'Closure update' block states that every P0 and P1 item is now closed, but it does not include a reference to the current PR or explicitly mention the closure of acceptance criterion #19 (which this PR addresses). Adding this reference would ensure the audit documentation accurately reflects the current state of the project and provides a complete audit trail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant