mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Compare commits
1 Commits
capture-sh
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c957b4bef1 |
16
codex-rs/tui/migration_journal_template.md
Normal file
16
codex-rs/tui/migration_journal_template.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Migration journal – {{MIGRATION_SUMMARY}}
|
||||
|
||||
> Workspace: `{{WORKSPACE_NAME}}`
|
||||
> Created: {{CREATED_AT}}
|
||||
|
||||
Use this log for async updates, agent hand-offs, and to publish what was learned during each workstream. Keep entries concise and focused on signals other collaborators need.
|
||||
|
||||
## Logging guidance
|
||||
- Start each entry with a timestamp and author/agent/workstream name.
|
||||
- Capture what changed, how it was validated, links to diffs/tests, and any open questions.
|
||||
- Highlight blockers, decisions needed, or knowledge that other agents should adopt.
|
||||
- Update the plan (`plan.md`) when scope changes; use this journal for progress + lessons.
|
||||
|
||||
| Timestamp | Agent / Workstream | Update / Learnings | Blockers & Risks | Next action / owner |
|
||||
| --------- | ------------------ | ------------------ | ---------------- | ------------------- |
|
||||
| | | | | |
|
||||
35
codex-rs/tui/migration_plan_template.md
Normal file
35
codex-rs/tui/migration_plan_template.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Migration plan – {{MIGRATION_SUMMARY}}
|
||||
|
||||
> Workspace: `{{WORKSPACE_NAME}}`
|
||||
> Generated: {{CREATED_AT}}
|
||||
|
||||
Use this document as the single source of truth for the migration effort. Keep it updated so any engineer (or agent) can jump in mid-flight.
|
||||
|
||||
## 1. Context & stakes
|
||||
- Current state snapshot
|
||||
- Target end state and deadline/launch windows
|
||||
- Guardrails, SLAs, compliance/regulatory constraints
|
||||
|
||||
## 2. Incremental plan (numbered)
|
||||
1. `[Step name]` — Purpose, scope, primary owner/skillset, upstream/downstream dependencies, validation & rollback signals.
|
||||
2. `…`
|
||||
|
||||
Each step must explain:
|
||||
- Preconditions & artifacts required before starting
|
||||
- Specific code/data/infrastructure changes
|
||||
- Telemetry, tests, or dry-runs that prove success
|
||||
|
||||
## 3. Parallel workstreams
|
||||
| Workstream | Objective | Inputs & dependencies | Ownership / skills | Progress & telemetry hooks |
|
||||
| ---------- | --------- | --------------------- | ------------------ | ------------------------- |
|
||||
| _Fill in during planning_ | | | | |
|
||||
|
||||
## 4. Data + rollout considerations
|
||||
- Data migration / backfill plan
|
||||
- Environment readiness, feature flags, or config toggles
|
||||
- Rollout plan (phases, smoke tests, canaries) and explicit rollback/kill-switch criteria
|
||||
|
||||
## 5. Risks, decisions, and follow-ups
|
||||
- Top risks with mitigation owners
|
||||
- Open questions / decisions with DRI and due date
|
||||
- Handoff expectations (reference `journal.md` for ongoing updates)
|
||||
25
codex-rs/tui/prompt_for_migrate_command.md
Normal file
25
codex-rs/tui/prompt_for_migrate_command.md
Normal file
@@ -0,0 +1,25 @@
|
||||
You are the migration showrunner orchestrating "{{MIGRATION_SUMMARY}}".
|
||||
|
||||
Workspace root: {{WORKSPACE_PATH}}
|
||||
Plan document to maintain: {{PLAN_PATH}}
|
||||
Shared journal for progress + learnings: {{JOURNAL_PATH}}
|
||||
|
||||
Before writing any code, study the repository layout, dependencies, release process, deployment gates, data stores, and cross-team stakeholders.
|
||||
|
||||
Deliverables:
|
||||
1. Start with a concise **Executive Overview** summarizing the target state, key risks, and the biggest unknowns.
|
||||
2. Provide an **Incremental plan** as a numbered list (1., 2., 3., …). Each item must include:
|
||||
- Objective and concrete changes required.
|
||||
- Dependencies or prerequisites, including approvals and data/state migrations.
|
||||
- Ownership expectations (skillsets/teams) and automation hooks.
|
||||
- Validation signals, telemetry, and rollback/kill-switch instructions.
|
||||
3. Add a **Parallel workstreams** table. Group tasks that can run concurrently, show how learnings are exchanged, and specify when progress should be published to {{JOURNAL_PATH}}.
|
||||
4. Capture **Coordination & learning loops**: how agents collaborate, what artifacts live in {{PLAN_PATH}} vs. {{JOURNAL_PATH}}, and how to keep successors unblocked.
|
||||
5. Outline a **Risk / data / rollout** section covering backfills, environment sequencing, feature flags, monitoring, and fallback criteria.
|
||||
|
||||
General guidance:
|
||||
- Call out missing information explicitly and request the files, owners, or metrics needed to proceed.
|
||||
- Highlight dependencies on other teams, scheduled freezes, or compliance gates.
|
||||
- Emphasize opportunities for automation, reuse of tooling, and knowledge sharing so multiple agents can contribute safely.
|
||||
|
||||
After sharing the plan in chat, mirror the same structure into {{PLAN_PATH}} (using apply_patch or editing tools) so it remains the canonical artifact. Encourage collaborators to keep {{JOURNAL_PATH}} updated with progress, blockers, and learnings.
|
||||
@@ -450,6 +450,9 @@ impl App {
|
||||
AppEvent::OpenReviewCustomPrompt => {
|
||||
self.chat_widget.show_review_custom_prompt();
|
||||
}
|
||||
AppEvent::StartMigration { summary } => {
|
||||
self.chat_widget.start_migration(summary);
|
||||
}
|
||||
AppEvent::FullScreenApprovalRequest(request) => match request {
|
||||
ApprovalRequest::ApplyPatch { cwd, changes, .. } => {
|
||||
let _ = tui.enter_alt_screen();
|
||||
|
||||
@@ -102,6 +102,11 @@ pub(crate) enum AppEvent {
|
||||
/// Open the custom prompt option from the review popup.
|
||||
OpenReviewCustomPrompt,
|
||||
|
||||
/// Kick off the `/migrate` workflow after the user names the migration.
|
||||
StartMigration {
|
||||
summary: String,
|
||||
},
|
||||
|
||||
/// Open the approval popup.
|
||||
FullScreenApprovalRequest(ApprovalRequest),
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -120,10 +121,14 @@ use codex_core::protocol::SandboxPolicy;
|
||||
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
|
||||
use codex_file_search::FileMatch;
|
||||
use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
use pathdiff::diff_paths;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
const USER_SHELL_COMMAND_HELP_TITLE: &str = "Prefix a command with ! to run it locally";
|
||||
const USER_SHELL_COMMAND_HELP_HINT: &str = "Example: !ls";
|
||||
const MIGRATION_PROMPT_TEMPLATE: &str = include_str!("../prompt_for_migrate_command.md");
|
||||
const MIGRATION_PLAN_TEMPLATE: &str = include_str!("../migration_plan_template.md");
|
||||
const MIGRATION_JOURNAL_TEMPLATE: &str = include_str!("../migration_journal_template.md");
|
||||
// Track information about an in-flight exec command.
|
||||
struct RunningCommand {
|
||||
command: Vec<String>,
|
||||
@@ -1234,6 +1239,9 @@ impl ChatWidget {
|
||||
const INIT_PROMPT: &str = include_str!("../prompt_for_init_command.md");
|
||||
self.submit_user_message(INIT_PROMPT.to_string().into());
|
||||
}
|
||||
SlashCommand::Migrate => {
|
||||
self.open_migrate_prompt();
|
||||
}
|
||||
SlashCommand::Compact => {
|
||||
self.clear_token_usage();
|
||||
self.app_event_tx.send(AppEvent::CodexOp(Op::Compact));
|
||||
@@ -2422,6 +2430,132 @@ impl ChatWidget {
|
||||
self.bottom_pane.show_view(Box::new(view));
|
||||
}
|
||||
|
||||
pub(crate) fn start_migration(&mut self, summary: String) {
|
||||
let summary = summary.trim();
|
||||
if summary.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.create_migration_workspace(summary) {
|
||||
Ok(workspace) => {
|
||||
let dir_display = self.relative_display_path(&workspace.dir_path);
|
||||
let plan_display = self.relative_display_path(&workspace.plan_path);
|
||||
let journal_display = self.relative_display_path(&workspace.journal_path);
|
||||
let prompt = self.build_migration_prompt(
|
||||
summary,
|
||||
&dir_display,
|
||||
&plan_display,
|
||||
&journal_display,
|
||||
);
|
||||
let hint = format!("Plan: {plan_display}\nJournal: {journal_display}",);
|
||||
self.add_info_message(
|
||||
format!(
|
||||
"Created migration workspace `{}` at {dir_display}.",
|
||||
workspace.dir_name
|
||||
),
|
||||
Some(hint),
|
||||
);
|
||||
self.submit_user_message(prompt.into());
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("failed to prepare migration workspace: {err}");
|
||||
self.add_error_message(format!("Failed to prepare migration workspace: {err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_migrate_prompt(&mut self) {
|
||||
let tx = self.app_event_tx.clone();
|
||||
let view = CustomPromptView::new(
|
||||
"Describe the migration".to_string(),
|
||||
"Example: Phase 2 – move billing to Postgres".to_string(),
|
||||
Some(
|
||||
"We'll create migration_<slug> with plan.md and journal.md once you press Enter."
|
||||
.to_string(),
|
||||
),
|
||||
Box::new(move |prompt: String| {
|
||||
let trimmed = prompt.trim().to_string();
|
||||
if trimmed.is_empty() {
|
||||
return;
|
||||
}
|
||||
tx.send(AppEvent::StartMigration { summary: trimmed });
|
||||
}),
|
||||
);
|
||||
self.bottom_pane.show_view(Box::new(view));
|
||||
}
|
||||
|
||||
fn create_migration_workspace(
|
||||
&self,
|
||||
summary: &str,
|
||||
) -> Result<MigrationWorkspace, std::io::Error> {
|
||||
let slug = sanitize_migration_slug(summary);
|
||||
let base_name = format!("migration_{slug}");
|
||||
let (dir_path, dir_name) = self.next_available_migration_dir(&base_name);
|
||||
fs::create_dir_all(&dir_path)?;
|
||||
let created_at = Local::now().format("%Y-%m-%d %H:%M %Z").to_string();
|
||||
let plan_path = dir_path.join("plan.md");
|
||||
let journal_path = dir_path.join("journal.md");
|
||||
let plan_contents = fill_template(
|
||||
MIGRATION_PLAN_TEMPLATE,
|
||||
&[
|
||||
("{{MIGRATION_SUMMARY}}", summary),
|
||||
("{{WORKSPACE_NAME}}", dir_name.as_str()),
|
||||
("{{CREATED_AT}}", created_at.as_str()),
|
||||
],
|
||||
);
|
||||
let journal_contents = fill_template(
|
||||
MIGRATION_JOURNAL_TEMPLATE,
|
||||
&[
|
||||
("{{MIGRATION_SUMMARY}}", summary),
|
||||
("{{WORKSPACE_NAME}}", dir_name.as_str()),
|
||||
("{{CREATED_AT}}", created_at.as_str()),
|
||||
],
|
||||
);
|
||||
fs::write(&plan_path, plan_contents)?;
|
||||
fs::write(&journal_path, journal_contents)?;
|
||||
Ok(MigrationWorkspace {
|
||||
dir_path,
|
||||
dir_name,
|
||||
plan_path,
|
||||
journal_path,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_migration_prompt(
|
||||
&self,
|
||||
summary: &str,
|
||||
dir_display: &str,
|
||||
plan_display: &str,
|
||||
journal_display: &str,
|
||||
) -> String {
|
||||
let replacements = [
|
||||
("{{MIGRATION_SUMMARY}}", summary),
|
||||
("{{WORKSPACE_PATH}}", dir_display),
|
||||
("{{PLAN_PATH}}", plan_display),
|
||||
("{{JOURNAL_PATH}}", journal_display),
|
||||
];
|
||||
fill_template(MIGRATION_PROMPT_TEMPLATE, &replacements)
|
||||
}
|
||||
|
||||
fn next_available_migration_dir(&self, base_name: &str) -> (PathBuf, String) {
|
||||
let mut candidate_name = base_name.to_string();
|
||||
let mut candidate_path = self.config.cwd.join(&candidate_name);
|
||||
let mut suffix = 2;
|
||||
while candidate_path.exists() {
|
||||
candidate_name = format!("{base_name}_{suffix:02}");
|
||||
candidate_path = self.config.cwd.join(&candidate_name);
|
||||
suffix += 1;
|
||||
}
|
||||
(candidate_path, candidate_name)
|
||||
}
|
||||
|
||||
fn relative_display_path(&self, path: &Path) -> String {
|
||||
diff_paths(path, &self.config.cwd)
|
||||
.unwrap_or_else(|| path.to_path_buf())
|
||||
.display()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn token_usage(&self) -> TokenUsage {
|
||||
self.token_info
|
||||
.as_ref()
|
||||
@@ -2580,6 +2714,48 @@ fn extract_first_bold(s: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
struct MigrationWorkspace {
|
||||
dir_path: PathBuf,
|
||||
dir_name: String,
|
||||
plan_path: PathBuf,
|
||||
journal_path: PathBuf,
|
||||
}
|
||||
|
||||
fn fill_template(template: &str, replacements: &[(&str, &str)]) -> String {
|
||||
let mut filled = template.to_string();
|
||||
for (needle, value) in replacements {
|
||||
filled = filled.replace(needle, value);
|
||||
}
|
||||
filled
|
||||
}
|
||||
|
||||
fn sanitize_migration_slug(summary: &str) -> String {
|
||||
let mut slug = String::new();
|
||||
let mut last_was_dash = true;
|
||||
for ch in summary.trim().to_lowercase().chars() {
|
||||
if ch.is_ascii_alphanumeric() {
|
||||
slug.push(ch);
|
||||
last_was_dash = false;
|
||||
} else if !last_was_dash {
|
||||
slug.push('-');
|
||||
last_was_dash = true;
|
||||
}
|
||||
}
|
||||
let mut trimmed = slug.trim_matches('-').to_string();
|
||||
if trimmed.len() > 48 {
|
||||
trimmed = trimmed
|
||||
.chars()
|
||||
.take(48)
|
||||
.collect::<String>()
|
||||
.trim_matches('-')
|
||||
.to_string();
|
||||
}
|
||||
if trimmed.is_empty() {
|
||||
return Local::now().format("plan-%Y%m%d-%H%M%S").to_string();
|
||||
}
|
||||
trimmed
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn show_review_commit_picker_with_entries(
|
||||
chat: &mut ChatWidget,
|
||||
|
||||
@@ -17,6 +17,7 @@ pub enum SlashCommand {
|
||||
Review,
|
||||
New,
|
||||
Init,
|
||||
Migrate,
|
||||
Compact,
|
||||
Undo,
|
||||
Diff,
|
||||
@@ -39,6 +40,7 @@ impl SlashCommand {
|
||||
SlashCommand::New => "start a new chat during a conversation",
|
||||
SlashCommand::Init => "create an AGENTS.md file with instructions for Codex",
|
||||
SlashCommand::Compact => "summarize conversation to prevent hitting the context limit",
|
||||
SlashCommand::Migrate => "spin up a migration workspace and planning prompt",
|
||||
SlashCommand::Review => "review my current changes and find issues",
|
||||
SlashCommand::Undo => "ask Codex to undo a turn",
|
||||
SlashCommand::Quit | SlashCommand::Exit => "exit Codex",
|
||||
@@ -65,6 +67,7 @@ impl SlashCommand {
|
||||
match self {
|
||||
SlashCommand::New
|
||||
| SlashCommand::Init
|
||||
| SlashCommand::Migrate
|
||||
| SlashCommand::Compact
|
||||
| SlashCommand::Undo
|
||||
| SlashCommand::Model
|
||||
|
||||
@@ -17,6 +17,7 @@ Control Codex’s behavior during an interactive session with slash commands.
|
||||
| `/review` | review my current changes and find issues |
|
||||
| `/new` | start a new chat during a conversation |
|
||||
| `/init` | create an AGENTS.md file with instructions for Codex |
|
||||
| `/migrate` | spin up a migration workspace and prompt Codex to plan it |
|
||||
| `/compact` | summarize conversation to prevent hitting the context limit |
|
||||
| `/undo` | ask Codex to undo a turn |
|
||||
| `/diff` | show git diff (including untracked files) |
|
||||
|
||||
Reference in New Issue
Block a user