mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-10 19:33:37 +08:00
feat(ecc2): persist file activity patch previews
This commit is contained in:
@@ -135,6 +135,7 @@ pub struct FileActivityEntry {
|
|||||||
pub path: String,
|
pub path: String,
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
pub diff_preview: Option<String>,
|
pub diff_preview: Option<String>,
|
||||||
|
pub patch_preview: Option<String>,
|
||||||
pub timestamp: DateTime<Utc>,
|
pub timestamp: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -761,6 +761,8 @@ impl StateStore {
|
|||||||
action: String,
|
action: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
diff_preview: Option<String>,
|
diff_preview: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
patch_preview: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = File::open(metrics_path)
|
let file = File::open(metrics_path)
|
||||||
@@ -803,6 +805,7 @@ impl StateStore {
|
|||||||
path,
|
path,
|
||||||
action: infer_file_activity_action(&row.tool_name),
|
action: infer_file_activity_action(&row.tool_name),
|
||||||
diff_preview: None,
|
diff_preview: None,
|
||||||
|
patch_preview: None,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
@@ -818,6 +821,7 @@ impl StateStore {
|
|||||||
action: parse_file_activity_action(&event.action)
|
action: parse_file_activity_action(&event.action)
|
||||||
.unwrap_or_else(|| infer_file_activity_action(&row.tool_name)),
|
.unwrap_or_else(|| infer_file_activity_action(&row.tool_name)),
|
||||||
diff_preview: normalize_optional_string(event.diff_preview),
|
diff_preview: normalize_optional_string(event.diff_preview),
|
||||||
|
patch_preview: normalize_optional_string(event.patch_preview),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@@ -1599,6 +1603,7 @@ impl StateStore {
|
|||||||
path,
|
path,
|
||||||
action: infer_file_activity_action(&tool_name),
|
action: infer_file_activity_action(&tool_name),
|
||||||
diff_preview: None,
|
diff_preview: None,
|
||||||
|
patch_preview: None,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@@ -1611,6 +1616,7 @@ impl StateStore {
|
|||||||
path: event.path,
|
path: event.path,
|
||||||
summary: summary.clone(),
|
summary: summary.clone(),
|
||||||
diff_preview: event.diff_preview,
|
diff_preview: event.diff_preview,
|
||||||
|
patch_preview: event.patch_preview,
|
||||||
timestamp: occurred_at,
|
timestamp: occurred_at,
|
||||||
});
|
});
|
||||||
if events.len() >= limit {
|
if events.len() >= limit {
|
||||||
@@ -1629,6 +1635,8 @@ struct PersistedFileEvent {
|
|||||||
action: FileActivityAction,
|
action: FileActivityAction,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
diff_preview: Option<String>,
|
diff_preview: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
patch_preview: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_persisted_file_events(value: &str) -> Option<Vec<PersistedFileEvent>> {
|
fn parse_persisted_file_events(value: &str) -> Option<Vec<PersistedFileEvent>> {
|
||||||
@@ -1644,6 +1652,7 @@ fn parse_persisted_file_events(value: &str) -> Option<Vec<PersistedFileEvent>> {
|
|||||||
path,
|
path,
|
||||||
action: event.action,
|
action: event.action,
|
||||||
diff_preview: normalize_optional_string(event.diff_preview),
|
diff_preview: normalize_optional_string(event.diff_preview),
|
||||||
|
patch_preview: normalize_optional_string(event.patch_preview),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -1959,7 +1968,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_file_activity_preserves_diff_previews() -> Result<()> {
|
fn list_file_activity_preserves_diff_and_patch_previews() -> Result<()> {
|
||||||
let tempdir = TestDir::new("store-file-activity-diffs")?;
|
let tempdir = TestDir::new("store-file-activity-diffs")?;
|
||||||
let db = StateStore::open(&tempdir.path().join("state.db"))?;
|
let db = StateStore::open(&tempdir.path().join("state.db"))?;
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
@@ -1984,7 +1993,7 @@ mod tests {
|
|||||||
fs::write(
|
fs::write(
|
||||||
&metrics_path,
|
&metrics_path,
|
||||||
concat!(
|
concat!(
|
||||||
"{\"id\":\"evt-1\",\"session_id\":\"session-1\",\"tool_name\":\"Edit\",\"input_summary\":\"Edit src/config.ts\",\"output_summary\":\"updated config\",\"file_paths\":[\"src/config.ts\"],\"file_events\":[{\"path\":\"src/config.ts\",\"action\":\"modify\",\"diff_preview\":\"API_URL=http://localhost:3000 -> API_URL=https://api.example.com\"}],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n"
|
"{\"id\":\"evt-1\",\"session_id\":\"session-1\",\"tool_name\":\"Edit\",\"input_summary\":\"Edit src/config.ts\",\"output_summary\":\"updated config\",\"file_paths\":[\"src/config.ts\"],\"file_events\":[{\"path\":\"src/config.ts\",\"action\":\"modify\",\"diff_preview\":\"API_URL=http://localhost:3000 -> API_URL=https://api.example.com\",\"patch_preview\":\"@@\\n- API_URL=http://localhost:3000\\n+ API_URL=https://api.example.com\"}],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n"
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -1998,6 +2007,10 @@ mod tests {
|
|||||||
activity[0].diff_preview.as_deref(),
|
activity[0].diff_preview.as_deref(),
|
||||||
Some("API_URL=http://localhost:3000 -> API_URL=https://api.example.com")
|
Some("API_URL=http://localhost:3000 -> API_URL=https://api.example.com")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
activity[0].patch_preview.as_deref(),
|
||||||
|
Some("@@\n- API_URL=http://localhost:3000\n+ API_URL=https://api.example.com")
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const PANE_RESIZE_STEP_PERCENT: u16 = 5;
|
|||||||
const MAX_LOG_ENTRIES: u64 = 12;
|
const MAX_LOG_ENTRIES: u64 = 12;
|
||||||
const MAX_DIFF_PREVIEW_LINES: usize = 6;
|
const MAX_DIFF_PREVIEW_LINES: usize = 6;
|
||||||
const MAX_DIFF_PATCH_LINES: usize = 80;
|
const MAX_DIFF_PATCH_LINES: usize = 80;
|
||||||
|
const MAX_FILE_ACTIVITY_PATCH_LINES: usize = 3;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
struct WorktreeDiffColumns {
|
struct WorktreeDiffColumns {
|
||||||
@@ -203,6 +204,7 @@ struct TimelineEvent {
|
|||||||
session_id: String,
|
session_id: String,
|
||||||
event_type: TimelineEventType,
|
event_type: TimelineEventType,
|
||||||
summary: String,
|
summary: String,
|
||||||
|
detail_lines: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -3410,19 +3412,26 @@ impl Dashboard {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|event| self.timeline_event_filter.matches(event.event_type))
|
.filter(|event| self.timeline_event_filter.matches(event.event_type))
|
||||||
.filter(|event| self.output_time_filter.matches_timestamp(event.occurred_at))
|
.filter(|event| self.output_time_filter.matches_timestamp(event.occurred_at))
|
||||||
.map(|event| {
|
.flat_map(|event| {
|
||||||
let prefix = if show_session_label {
|
let prefix = if show_session_label {
|
||||||
format!("{} ", format_session_id(&event.session_id))
|
format!("{} ", format_session_id(&event.session_id))
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
Line::from(format!(
|
let mut lines = vec![Line::from(format!(
|
||||||
"[{}] {}{:<11} {}",
|
"[{}] {}{:<11} {}",
|
||||||
event.occurred_at.format("%H:%M:%S"),
|
event.occurred_at.format("%H:%M:%S"),
|
||||||
prefix,
|
prefix,
|
||||||
event.event_type.label(),
|
event.event_type.label(),
|
||||||
event.summary
|
event.summary
|
||||||
))
|
))];
|
||||||
|
lines.extend(
|
||||||
|
event
|
||||||
|
.detail_lines
|
||||||
|
.into_iter()
|
||||||
|
.map(|line| Line::from(format!(" {}", line))),
|
||||||
|
);
|
||||||
|
lines
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -3459,6 +3468,7 @@ impl Dashboard {
|
|||||||
session.agent_type,
|
session.agent_type,
|
||||||
truncate_for_dashboard(&session.task, 64)
|
truncate_for_dashboard(&session.task, 64)
|
||||||
),
|
),
|
||||||
|
detail_lines: Vec::new(),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
if session.updated_at > session.created_at {
|
if session.updated_at > session.created_at {
|
||||||
@@ -3467,6 +3477,7 @@ impl Dashboard {
|
|||||||
session_id: session.id.clone(),
|
session_id: session.id.clone(),
|
||||||
event_type: TimelineEventType::Lifecycle,
|
event_type: TimelineEventType::Lifecycle,
|
||||||
summary: format!("state {} | updated session metadata", session.state),
|
summary: format!("state {} | updated session metadata", session.state),
|
||||||
|
detail_lines: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3479,6 +3490,7 @@ impl Dashboard {
|
|||||||
"attached worktree {} from {}",
|
"attached worktree {} from {}",
|
||||||
worktree.branch, worktree.base_branch
|
worktree.branch, worktree.base_branch
|
||||||
),
|
),
|
||||||
|
detail_lines: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3492,6 +3504,7 @@ impl Dashboard {
|
|||||||
session_id: session.id.clone(),
|
session_id: session.id.clone(),
|
||||||
event_type: TimelineEventType::FileChange,
|
event_type: TimelineEventType::FileChange,
|
||||||
summary: format!("files touched {}", session.metrics.files_changed),
|
summary: format!("files touched {}", session.metrics.files_changed),
|
||||||
|
detail_lines: Vec::new(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
events.extend(file_activity.into_iter().map(|entry| TimelineEvent {
|
events.extend(file_activity.into_iter().map(|entry| TimelineEvent {
|
||||||
@@ -3499,6 +3512,7 @@ impl Dashboard {
|
|||||||
session_id: session.id.clone(),
|
session_id: session.id.clone(),
|
||||||
event_type: TimelineEventType::FileChange,
|
event_type: TimelineEventType::FileChange,
|
||||||
summary: file_activity_summary(&entry),
|
summary: file_activity_summary(&entry),
|
||||||
|
detail_lines: file_activity_patch_lines(&entry, MAX_FILE_ACTIVITY_PATCH_LINES),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3525,6 +3539,7 @@ impl Dashboard {
|
|||||||
64
|
64
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
detail_lines: Vec::new(),
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -3544,6 +3559,7 @@ impl Dashboard {
|
|||||||
entry.duration_ms,
|
entry.duration_ms,
|
||||||
truncate_for_dashboard(&entry.input_summary, 56)
|
truncate_for_dashboard(&entry.input_summary, 56)
|
||||||
),
|
),
|
||||||
|
detail_lines: Vec::new(),
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
events
|
events
|
||||||
@@ -4148,6 +4164,9 @@ impl Dashboard {
|
|||||||
self.short_timestamp(&entry.timestamp.to_rfc3339()),
|
self.short_timestamp(&entry.timestamp.to_rfc3339()),
|
||||||
file_activity_summary(&entry)
|
file_activity_summary(&entry)
|
||||||
));
|
));
|
||||||
|
for detail in file_activity_patch_lines(&entry, 2) {
|
||||||
|
lines.push(format!(" {}", detail));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines.push(format!(
|
lines.push(format!(
|
||||||
@@ -5412,6 +5431,22 @@ fn file_activity_summary(entry: &FileActivityEntry) -> String {
|
|||||||
summary
|
summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn file_activity_patch_lines(entry: &FileActivityEntry, max_lines: usize) -> Vec<String> {
|
||||||
|
entry
|
||||||
|
.patch_preview
|
||||||
|
.as_deref()
|
||||||
|
.map(|patch| {
|
||||||
|
patch
|
||||||
|
.lines()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|line| !line.is_empty() && *line != "@@" && *line != "+" && *line != "-")
|
||||||
|
.take(max_lines)
|
||||||
|
.map(|line| truncate_for_dashboard(line, 72))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
fn file_activity_verb(action: crate::session::FileActivityAction) -> &'static str {
|
fn file_activity_verb(action: crate::session::FileActivityAction) -> &'static str {
|
||||||
match action {
|
match action {
|
||||||
crate::session::FileActivityAction::Read => "read",
|
crate::session::FileActivityAction::Read => "read",
|
||||||
@@ -6092,7 +6127,7 @@ mod tests {
|
|||||||
&metrics_path,
|
&metrics_path,
|
||||||
concat!(
|
concat!(
|
||||||
"{\"id\":\"evt-1\",\"session_id\":\"focus-12345678\",\"tool_name\":\"Read\",\"input_summary\":\"Read src/lib.rs\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\"],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n",
|
"{\"id\":\"evt-1\",\"session_id\":\"focus-12345678\",\"tool_name\":\"Read\",\"input_summary\":\"Read src/lib.rs\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\"],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n",
|
||||||
"{\"id\":\"evt-2\",\"session_id\":\"focus-12345678\",\"tool_name\":\"Write\",\"input_summary\":\"Write README.md\",\"output_summary\":\"updated readme\",\"file_paths\":[\"README.md\"],\"file_events\":[{\"path\":\"README.md\",\"action\":\"create\",\"diff_preview\":\"+ # ECC 2.0\"}],\"timestamp\":\"2026-04-09T00:01:00Z\"}\n"
|
"{\"id\":\"evt-2\",\"session_id\":\"focus-12345678\",\"tool_name\":\"Write\",\"input_summary\":\"Write README.md\",\"output_summary\":\"updated readme\",\"file_paths\":[\"README.md\"],\"file_events\":[{\"path\":\"README.md\",\"action\":\"create\",\"diff_preview\":\"+ # ECC 2.0\",\"patch_preview\":\"+ # ECC 2.0\\n+ \\n+ A richer dashboard\"}],\"timestamp\":\"2026-04-09T00:01:00Z\"}\n"
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
dashboard.db.sync_tool_activity_metrics(&metrics_path)?;
|
dashboard.db.sync_tool_activity_metrics(&metrics_path)?;
|
||||||
@@ -6103,12 +6138,14 @@ mod tests {
|
|||||||
assert!(rendered.contains("read src/lib.rs"));
|
assert!(rendered.contains("read src/lib.rs"));
|
||||||
assert!(rendered.contains("create README.md"));
|
assert!(rendered.contains("create README.md"));
|
||||||
assert!(rendered.contains("+ # ECC 2.0"));
|
assert!(rendered.contains("+ # ECC 2.0"));
|
||||||
|
assert!(rendered.contains("+ A richer dashboard"));
|
||||||
assert!(!rendered.contains("files touched 2"));
|
assert!(!rendered.contains("files touched 2"));
|
||||||
|
|
||||||
let metrics_text = dashboard.selected_session_metrics_text();
|
let metrics_text = dashboard.selected_session_metrics_text();
|
||||||
assert!(metrics_text.contains("Recent file activity"));
|
assert!(metrics_text.contains("Recent file activity"));
|
||||||
assert!(metrics_text.contains("create README.md"));
|
assert!(metrics_text.contains("create README.md"));
|
||||||
assert!(metrics_text.contains("+ # ECC 2.0"));
|
assert!(metrics_text.contains("+ # ECC 2.0"));
|
||||||
|
assert!(metrics_text.contains("+ A richer dashboard"));
|
||||||
assert!(metrics_text.contains("read src/lib.rs"));
|
assert!(metrics_text.contains("read src/lib.rs"));
|
||||||
|
|
||||||
let _ = fs::remove_dir_all(root);
|
let _ = fs::remove_dir_all(root);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ function pushPathCandidate(paths, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushFileEvent(events, value, action, diffPreview) {
|
function pushFileEvent(events, value, action, diffPreview, patchPreview) {
|
||||||
const candidate = String(value || '').trim();
|
const candidate = String(value || '').trim();
|
||||||
if (!candidate) {
|
if (!candidate) {
|
||||||
return;
|
return;
|
||||||
@@ -73,15 +73,22 @@ function pushFileEvent(events, value, action, diffPreview) {
|
|||||||
const normalizedDiffPreview = typeof diffPreview === 'string' && diffPreview.trim()
|
const normalizedDiffPreview = typeof diffPreview === 'string' && diffPreview.trim()
|
||||||
? diffPreview.trim()
|
? diffPreview.trim()
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const normalizedPatchPreview = typeof patchPreview === 'string' && patchPreview.trim()
|
||||||
|
? patchPreview.trim()
|
||||||
|
: undefined;
|
||||||
if (!events.some(event =>
|
if (!events.some(event =>
|
||||||
event.path === candidate
|
event.path === candidate
|
||||||
&& event.action === action
|
&& event.action === action
|
||||||
&& (event.diff_preview || undefined) === normalizedDiffPreview
|
&& (event.diff_preview || undefined) === normalizedDiffPreview
|
||||||
|
&& (event.patch_preview || undefined) === normalizedPatchPreview
|
||||||
)) {
|
)) {
|
||||||
const event = { path: candidate, action };
|
const event = { path: candidate, action };
|
||||||
if (normalizedDiffPreview) {
|
if (normalizedDiffPreview) {
|
||||||
event.diff_preview = normalizedDiffPreview;
|
event.diff_preview = normalizedDiffPreview;
|
||||||
}
|
}
|
||||||
|
if (normalizedPatchPreview) {
|
||||||
|
event.patch_preview = normalizedPatchPreview;
|
||||||
|
}
|
||||||
events.push(event);
|
events.push(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +100,19 @@ function sanitizeDiffText(value, maxLength = 96) {
|
|||||||
return truncateSummary(value, maxLength);
|
return truncateSummary(value, maxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizePatchLines(value, maxLines = 4, maxLineLength = 120) {
|
||||||
|
if (typeof value !== 'string' || !value.trim()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return stripAnsi(redactSecrets(value))
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map(line => line.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, maxLines)
|
||||||
|
.map(line => line.length <= maxLineLength ? line : `${line.slice(0, maxLineLength - 3)}...`);
|
||||||
|
}
|
||||||
|
|
||||||
function buildReplacementPreview(oldValue, newValue) {
|
function buildReplacementPreview(oldValue, newValue) {
|
||||||
const before = sanitizeDiffText(oldValue);
|
const before = sanitizeDiffText(oldValue);
|
||||||
const after = sanitizeDiffText(newValue);
|
const after = sanitizeDiffText(newValue);
|
||||||
@@ -116,6 +136,31 @@ function buildCreationPreview(content) {
|
|||||||
return `+ ${normalized}`;
|
return `+ ${normalized}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildPatchPreviewFromReplacement(oldValue, newValue) {
|
||||||
|
const beforeLines = sanitizePatchLines(oldValue);
|
||||||
|
const afterLines = sanitizePatchLines(newValue);
|
||||||
|
if (beforeLines.length === 0 && afterLines.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = ['@@'];
|
||||||
|
for (const line of beforeLines) {
|
||||||
|
lines.push(`- ${line}`);
|
||||||
|
}
|
||||||
|
for (const line of afterLines) {
|
||||||
|
lines.push(`+ ${line}`);
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPatchPreviewFromContent(content, prefix) {
|
||||||
|
const lines = sanitizePatchLines(content);
|
||||||
|
if (lines.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return lines.map(line => `${prefix} ${line}`).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
function inferDefaultFileAction(toolName) {
|
function inferDefaultFileAction(toolName) {
|
||||||
const normalized = String(toolName || '').trim().toLowerCase();
|
const normalized = String(toolName || '').trim().toLowerCase();
|
||||||
if (normalized.includes('read')) {
|
if (normalized.includes('read')) {
|
||||||
@@ -204,6 +249,26 @@ function fileEventDiffPreview(toolName, value, action) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fileEventPatchPreview(value, action) {
|
||||||
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value.old_string === 'string' || typeof value.new_string === 'string') {
|
||||||
|
return buildPatchPreviewFromReplacement(value.old_string, value.new_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'create') {
|
||||||
|
return buildPatchPreviewFromContent(value.content || value.file_text || value.text, '+');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'delete') {
|
||||||
|
return buildPatchPreviewFromContent(value.content || value.old_string || value.file_text, '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function collectFileEvents(toolName, value, events, key = null, parentValue = null) {
|
function collectFileEvents(toolName, value, events, key = null, parentValue = null) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
@@ -219,7 +284,13 @@ function collectFileEvents(toolName, value, events, key = null, parentValue = nu
|
|||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
if (key && FILE_PATH_KEYS.has(key)) {
|
if (key && FILE_PATH_KEYS.has(key)) {
|
||||||
const action = actionForFileKey(toolName, key);
|
const action = actionForFileKey(toolName, key);
|
||||||
pushFileEvent(events, value, action, fileEventDiffPreview(toolName, parentValue, action));
|
pushFileEvent(
|
||||||
|
events,
|
||||||
|
value,
|
||||||
|
action,
|
||||||
|
fileEventDiffPreview(toolName, parentValue, action),
|
||||||
|
fileEventPatchPreview(parentValue, action)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ function runTests() {
|
|||||||
path: 'src/config.ts',
|
path: 'src/config.ts',
|
||||||
action: 'modify',
|
action: 'modify',
|
||||||
diff_preview: 'API_URL=http://localhost:3000 -> API_URL=https://api.example.com',
|
diff_preview: 'API_URL=http://localhost:3000 -> API_URL=https://api.example.com',
|
||||||
|
patch_preview: '@@\n- API_URL=http://localhost:3000\n+ API_URL=https://api.example.com',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -196,11 +197,13 @@ function runTests() {
|
|||||||
path: 'src/a.ts',
|
path: 'src/a.ts',
|
||||||
action: 'modify',
|
action: 'modify',
|
||||||
diff_preview: 'const a = 1; -> const a = 2;',
|
diff_preview: 'const a = 1; -> const a = 2;',
|
||||||
|
patch_preview: '@@\n- const a = 1;\n+ const a = 2;',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'src/b.ts',
|
path: 'src/b.ts',
|
||||||
action: 'modify',
|
action: 'modify',
|
||||||
diff_preview: 'old name -> new name',
|
diff_preview: 'old name -> new name',
|
||||||
|
patch_preview: '@@\n- old name\n+ new name',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user