feat: implement history mode and AI development logging

- add dedicated history mode UI (toggle with `h`)
- allow navigation inside history with arrows
- allow selecting history entries with Enter
- add tests for history mode behavior
- introduce AI development logs (`ai-worklog.md`, `ai-prompts.md`)
- add AI worklog policy to `instructions-agent.md`
This commit is contained in:
2026-03-15 17:46:11 +01:00
parent 2bdfbe25d5
commit 65508d263f
5 changed files with 213 additions and 12 deletions

View File

@@ -22,6 +22,14 @@ class RenderLine:
is_selected: bool
@dataclass(frozen=True, slots=True)
class HistoryLine:
"""A rendered history row ready for terminal output."""
text: str
is_selected: bool
def move_selection(current_index: int, step: int, entry_count: int) -> int:
"""Move the selected index within the available entry range."""
@@ -57,11 +65,23 @@ def build_render_lines(navigator: Navigator) -> list[RenderLine]:
]
def build_history_lines(
entries: tuple[Path, ...], selected_index: int
) -> list[HistoryLine]:
"""Build all visible lines for history mode."""
return [
HistoryLine(text=str(path), is_selected=index == selected_index)
for index, path in enumerate(entries)
]
class TerminalUI:
"""Minimal curses UI for the directory navigator MVP."""
def __init__(self) -> None:
self._show_history_debug = False
self._history_mode = False
self._history_selected_index = 0
def run(self, navigator: Navigator) -> Path:
"""Start the interactive session and return the final directory path."""
@@ -81,6 +101,14 @@ class TerminalUI:
if key == ESCAPE_KEY:
return navigator.current_path
if key == ord("h"):
self._toggle_history_mode(navigator)
continue
if self._history_mode:
self._handle_history_key(key, navigator)
continue
if key == curses.KEY_UP:
navigator.set_selected_index(
move_selection(
@@ -117,10 +145,6 @@ class TerminalUI:
navigator.go_forward()
continue
if key == ord("h"):
self._show_history_debug = not self._show_history_debug
continue
if key in (curses.KEY_ENTER, 10, 13):
navigator.enter_selected_directory()
@@ -128,6 +152,11 @@ class TerminalUI:
stdscr.erase()
height, width = stdscr.getmaxyx()
if self._history_mode:
self._render_history_mode(stdscr, navigator, width=width, height=height)
stdscr.refresh()
return
path_text = str(navigator.current_path)
stdscr.addnstr(0, 0, path_text, width - 1)
@@ -138,25 +167,62 @@ class TerminalUI:
attributes = curses.A_REVERSE if line.is_selected else curses.A_NORMAL
stdscr.addnstr(row, 0, line.text, width - 1, attributes)
if self._show_history_debug:
self._render_history_debug(stdscr, navigator, width=width, height=height)
stdscr.refresh()
def _render_history_debug(
def _render_history_mode(
self, stdscr: curses.window, navigator: Navigator, width: int, height: int
) -> None:
history_entries = navigator.history.entries()
history_lines = build_history_lines(
navigator.history.entries(), self._history_selected_index
)
start_row = 1
stdscr.addnstr(start_row, 0, "History:", width - 1, curses.A_BOLD)
for index, path in enumerate(history_entries, start=1):
for index, line in enumerate(history_lines, start=1):
row = start_row + index
if row >= height:
break
stdscr.addnstr(row, 0, str(path), width - 1)
attributes = curses.A_REVERSE if line.is_selected else curses.A_NORMAL
stdscr.addnstr(row, 0, line.text, width - 1, attributes)
def _toggle_history_mode(self, navigator: Navigator) -> None:
self._history_mode = not self._history_mode
if self._history_mode:
self._history_selected_index = self._get_current_history_index(navigator)
def _handle_history_key(self, key: int, navigator: Navigator) -> None:
history_entries = navigator.history.entries()
if key == curses.KEY_UP:
self._history_selected_index = move_selection(
self._history_selected_index,
step=-1,
entry_count=len(history_entries),
)
return
if key == curses.KEY_DOWN:
self._history_selected_index = move_selection(
self._history_selected_index,
step=1,
entry_count=len(history_entries),
)
return
if key in (curses.KEY_ENTER, 10, 13) and history_entries:
navigator.select_history_entry(self._history_selected_index)
self._history_mode = False
def _get_current_history_index(self, navigator: Navigator) -> int:
history_entries = navigator.history.entries()
for index in range(len(history_entries) - 1, -1, -1):
if history_entries[index] == navigator.current_path:
return index
return 0
@contextmanager