Initial implementation of /init (#1822)
Basic /init command that appends an instruction to create AGENTS.md to the conversation history.
This commit is contained in:
40
INIT.md
Normal file
40
INIT.md
Normal file
@@ -0,0 +1,40 @@
|
||||
Generate a file named AGENTS.md that serves as a contributor guide for this repository.
|
||||
Your goal is to produce a clear, concise, and well-structured document with descriptive headings and actionable explanations for each section.
|
||||
Follow the outline below, but adapt as needed — add sections if relevant, and omit those that do not apply to this project.
|
||||
|
||||
Document Requirements
|
||||
|
||||
- Title the document "Repository Guidelines".
|
||||
- Use Markdown headings (#, ##, etc.) for structure.
|
||||
- Keep the document concise. 200-400 words is optimal.
|
||||
- Keep explanations short, direct, and specific to this repository.
|
||||
- Provide examples where helpful (commands, directory paths, naming patterns).
|
||||
- Maintain a professional, instructional tone.
|
||||
|
||||
Recommended Sections
|
||||
|
||||
Project Structure & Module Organization
|
||||
|
||||
- Outline the project structure, including where the source code, tests, and assets are located.
|
||||
|
||||
Build, Test, and Development Commands
|
||||
|
||||
- List key commands for building, testing, and running locally (e.g., npm test, make build).
|
||||
- Briefly explain what each command does.
|
||||
|
||||
Coding Style & Naming Conventions
|
||||
|
||||
- Specify indentation rules, language-specific style preferences, and naming patterns.
|
||||
- Include any formatting or linting tools used.
|
||||
|
||||
Testing Guidelines
|
||||
|
||||
- Identify testing frameworks and coverage requirements.
|
||||
- State test naming conventions and how to run tests.
|
||||
|
||||
Commit & Pull Request Guidelines
|
||||
|
||||
- Summarize commit message conventions found in the project’s Git history.
|
||||
- Outline pull request requirements (descriptions, linked issues, screenshots, etc.).
|
||||
|
||||
(Optional) Add other sections if relevant, such as Security & Configuration Tips, Architecture Overview, or Agent-Specific Instructions.
|
||||
@@ -300,6 +300,13 @@ impl App<'_> {
|
||||
self.app_state = AppState::Chat { widget: new_widget };
|
||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
SlashCommand::Init => {
|
||||
// Guard: do not run if a task is active.
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
const INIT_PROMPT: &str = include_str!("../../../INIT.md");
|
||||
widget.submit_text_message(INIT_PROMPT.to_string());
|
||||
}
|
||||
}
|
||||
SlashCommand::Compact => {
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.clear_token_usage();
|
||||
|
||||
@@ -729,6 +729,7 @@ impl WidgetRef for &ChatComposer {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::bottom_pane::AppEventSender;
|
||||
use crate::bottom_pane::ChatComposer;
|
||||
use crate::bottom_pane::InputResult;
|
||||
@@ -1004,6 +1005,49 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_init_dispatches_command_and_does_not_submit_literal_text() {
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer = ChatComposer::new(true, sender, false);
|
||||
|
||||
// Type the slash command.
|
||||
for ch in [
|
||||
'/', 'i', 'n', 'i', 't', // "/init"
|
||||
] {
|
||||
let _ = composer.handle_key_event(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE));
|
||||
}
|
||||
|
||||
// Press Enter to dispatch the selected command.
|
||||
let (result, _needs_redraw) =
|
||||
composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
// When a slash command is dispatched, the composer should not submit
|
||||
// literal text and should clear its textarea.
|
||||
match result {
|
||||
InputResult::None => {}
|
||||
InputResult::Submitted(text) => {
|
||||
panic!("expected command dispatch, but composer submitted literal text: {text}")
|
||||
}
|
||||
}
|
||||
assert!(composer.textarea.is_empty(), "composer should be cleared");
|
||||
|
||||
// Verify a DispatchCommand event for the "init" command was sent.
|
||||
match rx.try_recv() {
|
||||
Ok(AppEvent::DispatchCommand(cmd)) => {
|
||||
assert_eq!(cmd.command(), "init");
|
||||
}
|
||||
Ok(_other) => panic!("unexpected app event"),
|
||||
Err(TryRecvError::Empty) => panic!("expected a DispatchCommand event for '/init'"),
|
||||
Err(TryRecvError::Disconnected) => panic!("app event channel disconnected"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_pastes_submission() {
|
||||
use crossterm::event::KeyCode;
|
||||
|
||||
@@ -188,3 +188,38 @@ impl WidgetRef for CommandPopup {
|
||||
table.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn filter_includes_init_when_typing_prefix() {
|
||||
let mut popup = CommandPopup::new();
|
||||
// Simulate the composer line starting with '/in' so the popup filters
|
||||
// matching commands by prefix.
|
||||
popup.on_composer_text_change("/in".to_string());
|
||||
|
||||
// Access the filtered list via the selected command and ensure that
|
||||
// one of the matches is the new "init" command.
|
||||
let matches = popup.filtered_commands();
|
||||
assert!(
|
||||
matches.iter().any(|cmd| cmd.command() == "init"),
|
||||
"expected '/init' to appear among filtered commands"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selecting_init_by_exact_match() {
|
||||
let mut popup = CommandPopup::new();
|
||||
popup.on_composer_text_change("/init".to_string());
|
||||
|
||||
// When an exact match exists, the selected command should be that
|
||||
// command by default.
|
||||
let selected = popup.selected_command();
|
||||
match selected {
|
||||
Some(cmd) => assert_eq!(cmd.command(), "init"),
|
||||
None => panic!("expected a selected command for exact match"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,6 +575,16 @@ impl ChatWidget<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Programmatically submit a user text message as if typed in the
|
||||
/// composer. The text will be added to conversation history and sent to
|
||||
/// the agent.
|
||||
pub(crate) fn submit_text_message(&mut self, text: String) {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.submit_user_message(text.into());
|
||||
}
|
||||
|
||||
pub(crate) fn token_usage(&self) -> &TokenUsage {
|
||||
&self.token_usage
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ pub enum SlashCommand {
|
||||
// DO NOT ALPHA-SORT! Enum order is presentation order in the popup, so
|
||||
// more frequently used commands should be listed first.
|
||||
New,
|
||||
Init,
|
||||
Compact,
|
||||
Diff,
|
||||
Status,
|
||||
@@ -26,6 +27,7 @@ impl SlashCommand {
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
SlashCommand::New => "Start a new chat",
|
||||
SlashCommand::Init => "Create an AGENTS.md file with instructions for Codex.",
|
||||
SlashCommand::Compact => "Compact the chat history",
|
||||
SlashCommand::Quit => "Exit the application",
|
||||
SlashCommand::Diff => "Show git diff (including untracked files)",
|
||||
|
||||
Reference in New Issue
Block a user