Commit f69417a

mo khan <mo@mokhan.ca>
2026-01-29 18:07:46
chore: consolidate interview tool user stories into one
1 parent 8413d32
.elelem/backlog/006-interview-question-types.md → .elelem/backlog/archive/006-interview-question-types.md
File renamed without changes
.elelem/backlog/007-interview-batch-questions.md → .elelem/backlog/archive/007-interview-batch-questions.md
File renamed without changes
.elelem/backlog/008-interview-tui-selection.md → .elelem/backlog/archive/008-interview-tui-selection.md
File renamed without changes
.elelem/backlog/009-enhanced-interview-tool.md
@@ -0,0 +1,217 @@
+As an `agent`, I `want to ask questions with selectors and batch support`, so that `I can collect structured responses efficiently`.
+
+# SYNOPSIS
+
+Extend the interview tool to support text, single-select, and multi-select inputs with TUI navigation and batch question capability.
+
+# DESCRIPTION
+
+The interview tool currently only supports free-form text input. This story adds:
+
+1. **Input modes**:
+   - `text` - Free-form text input (current behavior, default)
+   - `select` - Radio-button style single choice from options
+   - `multi` - Checkbox style multiple choice from options
+
+2. **TUI interaction** (when terminal supports it):
+   - Arrow keys (up/down) to navigate between options
+   - Space to toggle selection (multi-select)
+   - Enter to confirm selection
+
+3. **Numbered fallback** (for dumb terminals or piped input):
+   - Display numbered list (e.g., "1. Red", "2. Green", "3. Blue")
+   - User types number to select
+   - Comma-separated numbers for multi-select (e.g., "1, 3")
+
+4. **Batch questions**:
+   - Accept array of questions in single call
+   - Present sequentially, collect all answers before returning
+
+# SEE ALSO
+
+* [ ] lib/elelem/terminal.rb - Add `select` and `multi_select` methods
+* [ ] lib/elelem/plugins/interview.rb - Add `options`, `multi`, and `questions` params
+
+# Tasks
+
+* [ ] TBD (filled in design mode)
+
+# Acceptance Criteria
+
+* [ ] Agent can provide `options` array to enable selector mode
+* [ ] Agent can set `multi: true` to allow multiple selections
+* [ ] Single-select with TUI: arrow keys navigate, enter confirms
+* [ ] Multi-select with TUI: arrow keys navigate, space toggles, enter confirms
+* [ ] Falls back to numbered list when terminal doesn't support TUI
+* [ ] Free-form text input still works when no options provided
+* [ ] Agent can pass `questions` array with multiple question objects
+* [ ] Each question in batch can have its own options and multi setting
+* [ ] Batch returns array of answers matching question order
+* [ ] Single-question API remains backward compatible
+* [ ] No new gem dependencies (uses io/console from stdlib)
+
+# Implementation Notes
+
+The following implementation plan is provided as guidance for the developer.
+
+## Tool Schema
+
+```json
+{
+  "question": { "type": "string", "description": "The question to ask" },
+  "options": { "type": "array", "description": "List of options (enables selector)" },
+  "multi": { "type": "boolean", "description": "Allow multiple selections" },
+  "questions": { "type": "array", "description": "Batch of question objects" }
+}
+```
+
+## Files to Modify
+
+- `lib/elelem/terminal.rb` - Add `select` and `multi_select` methods
+- `lib/elelem/plugins/interview.rb` - Add `options`, `multi`, and `questions` params
+
+## Terminal API
+
+```ruby
+# Single select - returns selected option string
+terminal.select(options) # => "option1"
+
+# Multi select - returns array of selected options
+terminal.multi_select(options) # => ["option1", "option3"]
+```
+
+## Reference Implementation
+
+### Terminal#select (single choice)
+
+```ruby
+def select(options)
+  return options.first if options.size == 1
+  require "io/console"
+
+  index = 0
+  render_options = -> {
+    options.each_with_index do |opt, i|
+      prefix = i == index ? "> " : "  "
+      $stdout.puts "#{prefix}#{opt}"
+    end
+  }
+
+  render_options.call
+
+  loop do
+    key = read_key
+    case key
+    when :up then index = (index - 1) % options.size
+    when :down then index = (index + 1) % options.size
+    when :enter then break
+    end
+    $stdout.print "\e[#{options.size}A\e[J"
+    render_options.call
+  end
+
+  options[index]
+end
+
+def read_key
+  char = $stdin.getch
+  return :enter if char == "\r" || char == "\n"
+  return char unless char == "\e"
+
+  return char unless $stdin.ready?
+  seq = $stdin.getch
+  return char unless seq == "["
+
+  code = $stdin.getch
+  case code
+  when "A" then :up
+  when "B" then :down
+  else char
+  end
+end
+```
+
+### Terminal#multi_select (multiple choice)
+
+```ruby
+def multi_select(options)
+  require "io/console"
+
+  index = 0
+  selected = Set.new
+
+  render_options = -> {
+    options.each_with_index do |opt, i|
+      cursor = i == index ? ">" : " "
+      check = selected.include?(i) ? "[x]" : "[ ]"
+      $stdout.puts "#{cursor} #{check} #{opt}"
+    end
+  }
+
+  render_options.call
+
+  loop do
+    key = read_key
+    case key
+    when :up then index = (index - 1) % options.size
+    when :down then index = (index + 1) % options.size
+    when :space then selected.include?(index) ? selected.delete(index) : selected.add(index)
+    when :enter then break
+    end
+    $stdout.print "\e[#{options.size}A\e[J"
+    render_options.call
+  end
+
+  options.values_at(*selected.to_a.sort)
+end
+```
+
+### Updated Interview Plugin
+
+```ruby
+Elelem::Plugins.register(:interview) do |agent|
+  agent.toolbox.add("interview",
+    description: "Ask the user a question and wait for their response",
+    params: {
+      question: { type: "string", description: "The question to ask" },
+      options: { type: "array", description: "List of options for selector" },
+      multi: { type: "boolean", description: "Allow multiple selections" },
+      questions: { type: "array", description: "Batch of question objects" }
+    },
+    required: ["question"]
+  ) do |args|
+    if args["questions"]&.any?
+      # Batch mode
+      answers = args["questions"].map do |q|
+        agent.terminal.say(agent.terminal.markdown(q["question"]))
+        ask_one(agent.terminal, q["options"], q["multi"])
+      end
+      { answers: answers }
+    else
+      # Single question mode
+      agent.terminal.say(agent.terminal.markdown(args["question"]))
+      answer = ask_one(agent.terminal, args["options"], args["multi"])
+      { answer: answer }
+    end
+  end
+
+  def ask_one(terminal, options, multi)
+    if options&.any?
+      multi ? terminal.multi_select(options) : terminal.select(options)
+    else
+      terminal.ask("> ")
+    end
+  end
+end
+```
+
+## Verification
+
+1. Run `./bin/run -p vertex`
+2. Ask the LLM to use the interview tool with options
+3. Test arrow key navigation
+4. Test single select (Enter confirms)
+5. Test multi select (Space toggles, Enter confirms)
+6. Test free-form text still works when no options provided
+7. Test batch questions with mixed types
+8. Run `bin/test`