Skip to content

Commit ea974a9

Browse files
mahabubul470claude
andcommitted
Phase 1: timeline capture + history promotion + line diff
gpp-core: - Add Changeset/Intent object type codes gpp-timeline (new): - SQLite (WAL) timeline DB per DATA_MODEL schema + workspace_state - Working-tree scanner: hash via gpp-core, mtime/size fast-path, add/modify/delete detection - .gitignore-style matcher (.gppignore + [timeline].ignore), .gpp/ always ignored - Debounced notify watcher; retention pruning; snapshot-tree builder gpp-history (new): - Author/Intent/Changeset objects (msgpack bodies, BLAKE3 ids) - Branch RefStore (refs/* + HEAD), promote (timeline -> changeset -> advance ref, mark entries promoted), changeset-DAG walk gpp-diff (new): - Line-based unified diff + per-file stats (similar); binary-aware gpp-cli: - timeline (list/watch/search/prune/export), promote, log, diff, branch (create/delete/switch/explore); status now shows real timeline/HEAD state - Deferred flags rejected with clear messages (semantic/interactive/sign/auto-summarize) Deps: + similar, globset, walkdir. Tests: core 21, timeline 8, history 5, diff 4, cli phase0 7, cli phase1 9. fmt + clippy -D warnings clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 01d83b1 commit ea974a9

27 files changed

Lines changed: 3164 additions & 32 deletions

File tree

Cargo.lock

Lines changed: 375 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ tracing-subscriber = "0.3"
6363
# File system
6464
notify = "7"
6565
tempfile = "3"
66+
globset = "0.4"
67+
walkdir = "2"
68+
69+
# Diff
70+
similar = "2"
6671

6772
# Tree-sitter
6873
tree-sitter = "0.24"

README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,38 @@ full specification (architecture, data model, CLI, protocols, roadmap).
99

1010
## Status
1111

12-
**Phase 0 (Foundation)**in progress. See [`docs/ROADMAP.md`](docs/ROADMAP.md).
12+
**Phase 1 (Timeline + Basic History)**complete. See [`docs/ROADMAP.md`](docs/ROADMAP.md).
1313

1414
Implemented:
1515

16-
- `gpp-core` — content-addressed object store (BLAKE3 + zstd), `Blob` / `Tree`
17-
objects, atomic idempotent writes, hash-verified reads.
18-
- `gpp-cli` — the `gpp` binary with `init`, `status`, and `config`.
16+
- `gpp-core` — content-addressed object store (BLAKE3 + zstd); `Blob`, `Tree`,
17+
`Changeset`, `Intent` object types; atomic idempotent writes; hash-verified reads.
18+
- `gpp-timeline` — SQLite (WAL) timeline DB, working-tree scanner, `.gppignore`
19+
+ configured ignore matching, debounced `notify` watcher, retention pruning.
20+
- `gpp-history``Changeset`/`Intent`/`Author` objects, branch `RefStore`,
21+
promote (timeline → changeset), changeset-DAG walk.
22+
- `gpp-diff` — line-based unified diff + stats (semantic diff is Phase 2).
23+
- `gpp-cli` — the `gpp` binary: `init`, `status`, `config`, `timeline`
24+
(list/watch/search/prune/export), `promote`, `log`, `diff`, `branch`.
1925
- CI: `cargo fmt`, `cargo clippy -D warnings`, `cargo test`.
2026

21-
All other crates in the workspace are compiling stubs filled in by later phases.
27+
Deferred to later phases (rejected with a clear message if invoked): semantic
28+
diff, `promote --interactive/--auto-summarize/--sign`, Git bridge, AI features.
29+
The remaining workspace crates are compiling stubs filled in by later phases.
2230

2331
## Build
2432

2533
```bash
2634
cargo build --release
2735
cargo test --workspace
2836

29-
# Try it
37+
# Try it (Phase 1 solo-dev flow)
3038
cargo run --bin gpp -- init --graphex
39+
echo "fn main() {}" > main.rs
40+
cargo run --bin gpp -- timeline # see continuous capture
41+
cargo run --bin gpp -- promote -m "first cut" --intent feature
42+
cargo run --bin gpp -- log --oneline
43+
cargo run --bin gpp -- diff
44+
cargo run --bin gpp -- branch create feature/x
3145
cargo run --bin gpp -- status
32-
cargo run --bin gpp -- config get trust.auto_merge_min
3346
```

crates/gpp-cli/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ path = "src/main.rs"
1212

1313
[dependencies]
1414
gpp-core = { path = "../gpp-core" }
15+
gpp-timeline = { path = "../gpp-timeline" }
16+
gpp-history = { path = "../gpp-history" }
17+
gpp-diff = { path = "../gpp-diff" }
18+
globset.workspace = true
1519
clap.workspace = true
1620
anyhow.workspace = true
1721
serde.workspace = true

crates/gpp-cli/src/cli.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,159 @@ pub enum Command {
5555
Status(StatusArgs),
5656
/// View and edit configuration
5757
Config(ConfigArgs),
58+
/// View and manage the continuous timeline
59+
Timeline(TimelineArgs),
60+
/// Promote timeline entries to a curated changeset
61+
Promote(PromoteArgs),
62+
/// View changeset history
63+
Log(LogArgs),
64+
/// Show changes (line-based; semantic diff arrives in Phase 2)
65+
Diff(DiffArgs),
66+
/// Manage branches and agent exploration branches
67+
Branch(BranchArgs),
68+
}
69+
70+
#[derive(Args)]
71+
pub struct TimelineArgs {
72+
#[command(subcommand)]
73+
pub action: Option<TimelineAction>,
74+
75+
/// Show entries since this time (e.g. "1h", "2d", "today", "2026-05-16")
76+
#[arg(long, global = true)]
77+
pub since: Option<String>,
78+
/// Show entries until this time
79+
#[arg(long, global = true)]
80+
pub until: Option<String>,
81+
/// Filter by author id
82+
#[arg(long, global = true)]
83+
pub author: Option<String>,
84+
/// Filter by file path glob
85+
#[arg(long, global = true, value_name = "PATTERN")]
86+
pub file: Option<String>,
87+
/// Show last N entries
88+
#[arg(short = 'n', long, global = true, default_value_t = 20)]
89+
pub limit: u32,
90+
/// Show per-file change details
91+
#[arg(long, global = true)]
92+
pub stat: bool,
93+
}
94+
95+
#[derive(Subcommand)]
96+
pub enum TimelineAction {
97+
/// Live-stream timeline entries as they happen
98+
Watch,
99+
/// Search timeline by file/author
100+
Search,
101+
/// Remove old timeline entries per retention policy
102+
Prune {
103+
/// Override retention (e.g. "60d", "24h")
104+
#[arg(long)]
105+
older_than: Option<String>,
106+
},
107+
/// Export the (filtered) timeline as JSON
108+
Export {
109+
/// Output file (default: stdout)
110+
path: Option<PathBuf>,
111+
},
112+
}
113+
114+
#[derive(Args)]
115+
pub struct PromoteArgs {
116+
/// Start of timeline range (entry id or time)
117+
#[arg(long)]
118+
pub from: Option<String>,
119+
/// End of timeline range (entry id or time)
120+
#[arg(long)]
121+
pub to: Option<String>,
122+
/// Changeset description
123+
#[arg(short, long)]
124+
pub message: Option<String>,
125+
/// Intent type: feature|bugfix|refactor|docs|dependency
126+
#[arg(long)]
127+
pub intent: Option<String>,
128+
/// Link to a task/issue
129+
#[arg(long)]
130+
pub task: Option<String>,
131+
/// Interactively select entries (Phase 1: not implemented)
132+
#[arg(short, long)]
133+
pub interactive: bool,
134+
/// Use AI to summarize (later phase: not implemented)
135+
#[arg(long)]
136+
pub auto_summarize: bool,
137+
/// Cryptographically sign (later phase: not implemented)
138+
#[arg(long)]
139+
pub sign: bool,
140+
}
141+
142+
#[derive(Args)]
143+
pub struct LogArgs {
144+
/// One line per changeset
145+
#[arg(long)]
146+
pub oneline: bool,
147+
/// Show a simple ASCII graph column
148+
#[arg(long)]
149+
pub graph: bool,
150+
/// Show semantic change summaries (Phase 2: not available)
151+
#[arg(long)]
152+
pub semantic: bool,
153+
/// Filter by author id
154+
#[arg(long)]
155+
pub author: Option<String>,
156+
/// Only agent-authored changesets
157+
#[arg(long)]
158+
pub agent: bool,
159+
/// Only human-authored changesets
160+
#[arg(long)]
161+
pub human: bool,
162+
/// Filter by intent type
163+
#[arg(long)]
164+
pub intent: Option<String>,
165+
#[arg(long)]
166+
pub since: Option<String>,
167+
#[arg(long)]
168+
pub until: Option<String>,
169+
/// Show last N changesets
170+
#[arg(short = 'n', long, default_value_t = 20)]
171+
pub limit: usize,
172+
}
173+
174+
#[derive(Args)]
175+
pub struct DiffArgs {
176+
/// Target: empty (working vs HEAD), <changeset>, or <cs1>..<cs2>
177+
pub target: Option<String>,
178+
/// Force line-based diff (the only mode in Phase 1)
179+
#[arg(long)]
180+
pub line: bool,
181+
/// Show semantic operations (Phase 2: falls back to line)
182+
#[arg(long)]
183+
pub semantic: bool,
184+
/// Show only statistics
185+
#[arg(long)]
186+
pub stat: bool,
187+
/// Show only file names
188+
#[arg(long)]
189+
pub files: bool,
190+
}
191+
192+
#[derive(Args)]
193+
pub struct BranchArgs {
194+
#[command(subcommand)]
195+
pub action: Option<BranchAction>,
196+
/// Show all branches including explorations
197+
#[arg(short, long)]
198+
pub all: bool,
199+
}
200+
201+
#[derive(Subcommand)]
202+
pub enum BranchAction {
203+
/// Create a new branch at the current tip
204+
Create { name: String },
205+
/// Delete a branch
206+
Delete { name: String },
207+
/// Switch to a branch
208+
Switch { name: String },
209+
/// Create an exploration branch (explorations/<name>)
210+
Explore { name: String },
58211
}
59212

60213
#[derive(Args)]

crates/gpp-cli/src/commands.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,22 @@ pub fn status(args: &StatusArgs, repo_override: Option<&Path>, json: bool) -> Re
9696
.and_then(toml::Value::as_bool)
9797
.unwrap_or(true);
9898

99+
let (entries, unpromoted, tip) = crate::phase1::summarize(&repo)?;
100+
let tip_str = tip
101+
.clone()
102+
.map(|s| format!("cs:{s}"))
103+
.unwrap_or_else(|| "(no changesets yet)".into());
104+
99105
if json {
100106
let out = serde_json::json!({
101107
"branch": branch,
108+
"head": tip,
102109
"objects": objects,
103110
"timeline": {
104111
"enabled": timeline_enabled,
105-
"entries": 0,
106-
"status": "not-implemented (Phase 1)",
112+
"entries": entries,
107113
},
108-
"unpromoted_changes": 0,
114+
"unpromoted_changes": unpromoted,
109115
"active_agents": [],
110116
"policy_violations": 0,
111117
"session_cost_microdollars": 0,
@@ -115,21 +121,21 @@ pub fn status(args: &StatusArgs, repo_override: Option<&Path>, json: bool) -> Re
115121
}
116122

117123
if args.short {
118-
println!("{branch} · {objects} objects · 0 unpromoted");
124+
println!("{branch} · {objects} objects · {unpromoted} unpromoted");
119125
return Ok(());
120126
}
121127

122-
println!("On branch: {branch}");
128+
println!("On branch: {branch} ({tip_str})");
123129
println!("Objects: {objects} stored");
124130
println!(
125-
"Timeline: {} (0 entries — capture engine lands in Phase 1)",
131+
"Timeline: {} ({entries} entries)",
126132
if timeline_enabled {
127133
"enabled"
128134
} else {
129135
"disabled"
130136
}
131137
);
132-
println!("Unpromoted changes: 0");
138+
println!("Unpromoted changes: {unpromoted}");
133139
println!("Active agents: none");
134140
println!("Policy violations: 0");
135141
println!("Cost this session: $0.00");

crates/gpp-cli/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
mod cli;
88
mod commands;
99
mod config;
10+
mod phase1;
1011
mod repo;
1112

1213
use std::process::ExitCode;
@@ -25,6 +26,11 @@ fn main() -> ExitCode {
2526
Command::Init(a) => commands::init(a, args.json, args.quiet),
2627
Command::Status(a) => commands::status(a, repo_override, args.json),
2728
Command::Config(a) => commands::config(a, repo_override, args.quiet),
29+
Command::Timeline(a) => phase1::timeline(a, repo_override, args.json),
30+
Command::Promote(a) => phase1::promote(a, repo_override),
31+
Command::Log(a) => phase1::log(a, repo_override),
32+
Command::Diff(a) => phase1::diff(a, repo_override),
33+
Command::Branch(a) => phase1::branch(a, repo_override),
2834
};
2935

3036
match result {

0 commit comments

Comments
 (0)