Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions docs/extensions/reflect.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,9 @@ full product direction:
- when no project or repo is provided outside a Git repository, it defaults to
personal scope over the recent `30d` window;
- it renders project and personal conversation timelines, chunks long sessions,
filters low-level transcript artifacts, and emits a small discussion-prompt
layer.
filters low-level transcript artifacts, emits deterministic source-role,
project-activity, and task-shape summaries, and emits a small
discussion-prompt layer.

The implemented baseline does not yet support a TUI output mode, proposal
persistence, or instruction-file patch application.
Expand Down Expand Up @@ -279,11 +280,14 @@ Recall reflect for <scope>:

1. Scope summary
2. Timeline
3. Observed workflow patterns
4. Discussion prompts
5. Calibration targets confirmed by the user
6. Optional proposals
7. Follow-up checks from previous calibrations
3. Source roles
4. Project activity
5. Task shapes
6. Observed workflow patterns
7. Discussion prompts
8. Calibration targets confirmed by the user
9. Optional proposals
10. Follow-up checks from previous calibrations
```

The main timeline should read like a concise history of human intent and agent
Expand All @@ -307,9 +311,26 @@ conversation:
{
"source": "claude-code",
"observed_role": "Exploration and broad planning",
"sessions": 2,
"timeline_moments": 6,
"evidence_moments": ["moment-1", "moment-3"]
}
],
"project_summaries": [
{
"project": "/path/to/repo",
"sessions": 3,
"timeline_moments": 12,
"sources": ["codex", "claude-code"]
}
],
"task_shapes": [
{
"shape": "planning",
"timeline_moments": 4,
"evidence_moments": ["moment-1", "moment-5"]
}
],
"timeline": [
{
"id": "moment-1",
Expand Down Expand Up @@ -369,7 +390,8 @@ the data plane and query protocol.
- `recall-reflect` is an official extension binary with manifest support.
- Project and personal reflection work from `metadata,messages`.
- Text and JSON output render the selected scope kind, project/repo scope,
timeline phases, observed patterns, and proposal stubs.
timeline phases, source-role summaries, project-activity summaries, task-shape
summaries, observed patterns, and proposal stubs.
- Low-level transcript artifacts are hidden or summarized by default.

### Completed Milestone: Personal And Project Scopes
Expand All @@ -381,17 +403,21 @@ the data plane and query protocol.
the recent `30d` time window.
- Text and JSON output include a stable scope kind.

### Next Milestone: Broader Reflection Signals
### Completed Milestone: Broader Reflection Signals

- Personal reflection provides deterministic source, project, and task-shape
summaries without reading SQLite directly.

### Later Milestone: Interactive Review
### Next Milestone: Interactive Review

- `recall reflect` opens the TUI by default.
- `recall reflect` can open a TUI review workbench.
- TUI shows actionable observations with evidence ids.
- TUI previews instruction-file patches.
- Apply writes only approved instruction-file patches.

### Later Milestone: Interactive Review Polish

- `recall reflect` opens the TUI by default.
- `--format json` emits machine-readable scope, observations, evidence, and
proposal stubs.

Expand Down
27 changes: 27 additions & 0 deletions extensions/recall-reflect/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,30 @@ pub struct ObservedPattern {
pub discussion_prompt: String,
}

#[derive(Debug, Clone, Serialize)]
pub struct SourceRoleSummary {
pub source: String,
pub observed_role: String,
pub sessions: usize,
pub timeline_moments: usize,
pub evidence_moments: Vec<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct ProjectActivitySummary {
pub project: String,
pub sessions: usize,
pub timeline_moments: usize,
pub sources: Vec<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct TaskShapeSummary {
pub shape: String,
pub timeline_moments: usize,
pub evidence_moments: Vec<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct ReflectProposalStub {
pub note: String,
Expand All @@ -122,6 +146,9 @@ pub struct ReflectReport {
pub summary: ReflectSummary,
pub chunks: Vec<ConversationChunk>,
pub phases: Vec<TimelinePhase>,
pub source_roles: Vec<SourceRoleSummary>,
pub project_summaries: Vec<ProjectActivitySummary>,
pub task_shapes: Vec<TaskShapeSummary>,
pub observed_patterns: Vec<ObservedPattern>,
pub proposals: Vec<ReflectProposalStub>,
pub coverage_note: Option<String>,
Expand Down
68 changes: 68 additions & 0 deletions extensions/recall-reflect/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,43 @@ pub fn render_text(report: &ReflectReport) -> String {
let _ = writeln!(out);
}

if !report.source_roles.is_empty() {
let _ = writeln!(out, "Source Roles");
for role in &report.source_roles {
let _ = writeln!(
out,
" - {}: {} ({} sessions, {} moments)",
role.source, role.observed_role, role.sessions, role.timeline_moments
);
}
let _ = writeln!(out);
}

if !report.project_summaries.is_empty() {
let _ = writeln!(out, "Project Activity");
for project in &report.project_summaries {
let sources = if project.sources.is_empty() {
"-".to_string()
} else {
project.sources.join(", ")
};
let _ = writeln!(
out,
" - {}: {} sessions, {} moments ({})",
project.project, project.sessions, project.timeline_moments, sources
);
}
let _ = writeln!(out);
}

if !report.task_shapes.is_empty() {
let _ = writeln!(out, "Task Shapes");
for shape in &report.task_shapes {
let _ = writeln!(out, " - {}: {} moments", shape.shape, shape.timeline_moments);
}
let _ = writeln!(out);
}

if !report.observed_patterns.is_empty() {
let _ = writeln!(out, "Discussion Prompts");
for pattern in &report.observed_patterns {
Expand Down Expand Up @@ -130,4 +167,35 @@ mod tests {
assert!(text.contains("hi there"), "must include assistant moment content");
assert!(!text.contains("session_events"), "must not contain raw event names");
}

#[test]
fn reflect_text_output_includes_broader_signals() {
let sessions = vec![fixture_session(
"s1",
"codex",
"Planning session",
1000,
vec![fixture_message(
"assistant",
"Plan the approach, outline the options, and analyze tradeoffs",
0,
1100,
)],
)];

let report = build_reflect_report(sessions, &ReflectFilters::default());
let text = render_text(&report);

let timeline = text.find("Timeline").unwrap();
let source_roles = text.find("Source Roles").unwrap();
let project_activity = text.find("Project Activity").unwrap();
let task_shapes = text.find("Task Shapes").unwrap();

assert!(timeline < source_roles);
assert!(source_roles < project_activity);
assert!(project_activity < task_shapes);
assert!(text.contains("codex: Planning and analysis"));
assert!(text.contains("/tmp/reflect-repo"));
assert!(text.contains("planning"));
}
}
Loading