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_state = AppState::Chat { widget: new_widget };
|
||||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
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 => {
|
SlashCommand::Compact => {
|
||||||
if let AppState::Chat { widget } = &mut self.app_state {
|
if let AppState::Chat { widget } = &mut self.app_state {
|
||||||
widget.clear_token_usage();
|
widget.clear_token_usage();
|
||||||
|
|||||||
@@ -729,6 +729,7 @@ impl WidgetRef for &ChatComposer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::app_event::AppEvent;
|
||||||
use crate::bottom_pane::AppEventSender;
|
use crate::bottom_pane::AppEventSender;
|
||||||
use crate::bottom_pane::ChatComposer;
|
use crate::bottom_pane::ChatComposer;
|
||||||
use crate::bottom_pane::InputResult;
|
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]
|
#[test]
|
||||||
fn test_multiple_pastes_submission() {
|
fn test_multiple_pastes_submission() {
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
|
|||||||
@@ -188,3 +188,38 @@ impl WidgetRef for CommandPopup {
|
|||||||
table.render(area, buf);
|
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 {
|
pub(crate) fn token_usage(&self) -> &TokenUsage {
|
||||||
&self.token_usage
|
&self.token_usage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub enum SlashCommand {
|
|||||||
// DO NOT ALPHA-SORT! Enum order is presentation order in the popup, so
|
// DO NOT ALPHA-SORT! Enum order is presentation order in the popup, so
|
||||||
// more frequently used commands should be listed first.
|
// more frequently used commands should be listed first.
|
||||||
New,
|
New,
|
||||||
|
Init,
|
||||||
Compact,
|
Compact,
|
||||||
Diff,
|
Diff,
|
||||||
Status,
|
Status,
|
||||||
@@ -26,6 +27,7 @@ impl SlashCommand {
|
|||||||
pub fn description(self) -> &'static str {
|
pub fn description(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
SlashCommand::New => "Start a new chat",
|
SlashCommand::New => "Start a new chat",
|
||||||
|
SlashCommand::Init => "Create an AGENTS.md file with instructions for Codex.",
|
||||||
SlashCommand::Compact => "Compact the chat history",
|
SlashCommand::Compact => "Compact the chat history",
|
||||||
SlashCommand::Quit => "Exit the application",
|
SlashCommand::Quit => "Exit the application",
|
||||||
SlashCommand::Diff => "Show git diff (including untracked files)",
|
SlashCommand::Diff => "Show git diff (including untracked files)",
|
||||||
|
|||||||
Reference in New Issue
Block a user