1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// SPDX-License-Identifier: Apache-2.0
//! Bridge command definitions.
use std::path::PathBuf;
use clap::Subcommand;
/// Source for a git import: either a local filesystem path or a URL that
/// sley can fetch from.
///
/// We discriminate by inspecting the input string: anything containing
/// `://` (https/ssh/git/file URLs) or starting with `git@` (ssh shorthand)
/// is treated as a URL; everything else is a local path. This keeps the
/// rules predictable — `/tmp/foo` always means a path, and a stray
/// `git@host:path` shorthand never gets misread as a relative path.
#[derive(Debug, Clone)]
pub enum GitSource {
Path(PathBuf),
Url(String),
}
impl GitSource {
pub fn parse(s: &str) -> Result<Self, String> {
if s.contains("://") || s.starts_with("git@") {
Ok(GitSource::Url(s.to_string()))
} else {
Ok(GitSource::Path(PathBuf::from(s)))
}
}
pub fn display(&self) -> String {
match self {
GitSource::Path(p) => p.display().to_string(),
GitSource::Url(u) => u.clone(),
}
}
}
fn parse_git_source(s: &str) -> Result<GitSource, String> {
GitSource::parse(s)
}
#[derive(Subcommand, Clone)]
pub enum BridgeCommands {
/// Git bridge operations.
Git {
#[command(subcommand)]
command: GitCommands,
},
}
#[derive(Subcommand, Clone)]
pub enum GitCommands {
/// Show the current state of the Git overlay bridge.
///
/// Reports the import-hint surface (which Git branches are visible
/// only on the Git side and need a `bridge import` run), the active
/// branch on the Git side, and any pending bridge operation. This
/// is the canonical place to consume bridge-status information for
/// scripts; other `--output json` outputs intentionally omit it.
Status,
/// Initialize Git mirror.
Init {
/// Path to existing Git repository (optional).
#[arg(long)]
path: Option<std::path::PathBuf>,
},
/// Export Heddle states to Git.
///
/// Writes a complete bare git repository at `--destination` containing
/// every reachable Heddle state as a git commit, with branches and tags
/// mirroring Heddle's threads and markers.
Export {
/// Destination path for the exported git repository. Must be writable;
/// will be initialized as a bare repo if it does not already exist.
#[arg(short, long)]
destination: Option<std::path::PathBuf>,
},
/// Import Git commits to Heddle.
///
/// Walks local branches and tags by default. To import remote-tracking
/// refs (`refs/remotes/*`), name them explicitly with `--ref`.
/// Reflog-only history is not part of the public bridge import surface.
///
/// `--path` accepts either a local filesystem path
/// (`/tmp/some-repo`, `./.git`) or a git URL — `https://...`,
/// `ssh://...`, `git://...`, `git@host:owner/repo.git`, or
/// `file://...`. URL imports are cloned into a heddle-managed temp
/// directory and then imported the same way local imports are.
/// Authentication for private URL sources uses Git-compatible
/// credential files/config and the host SSH agent when available.
Import {
/// Local path or git URL to import from.
#[arg(short, long, value_parser = parse_git_source)]
path: Option<GitSource>,
/// Ref names to import (repeatable). Scopes the import to the
/// listed branches, tags, or remote-tracking refs; omit to
/// import all branches and tags.
#[arg(long = "ref", value_name = "REF")]
refs: Vec<String>,
/// Accept git tree entries Heddle cannot represent losslessly.
///
/// By default import fails on the first unrepresentable tree entry
/// and names the offending path. With this flag, import restores the
/// historical drop/convert behavior and prints an end-of-run summary
/// of every affected entry.
#[arg(long)]
lossy: bool,
},
/// Bidirectional sync with Git (export + import).
Sync {
/// Local path or git URL to sync with.
#[arg(short, long, value_parser = parse_git_source)]
path: Option<GitSource>,
},
/// Preview a recovery path when a Git branch and Heddle thread diverge.
Reconcile {
/// Which local side should be treated as authoritative when applying.
///
/// Omit with `--preview` to inspect both local repair choices without
/// changing refs, remotes, index, or worktree files.
#[arg(long, value_parser = ["git", "heddle"])]
prefer: Option<String>,
/// Branch/ref to reconcile.
#[arg(long = "ref", value_name = "BRANCH")]
ref_name: String,
/// Show the planned recovery without changing refs.
#[arg(long)]
preview: bool,
},
/// Push to Git remote.
Push {
/// Remote name (default: origin).
remote: Option<String>,
},
/// Pull from Git remote.
Pull {
/// Remote name (default: origin).
remote: Option<String>,
},
/// Mine local AI-coding-agent sessions (Claude / Codex / OpenCode)
/// for reasoning notecards and attach them as `context` annotations
/// to the matching imported states. Requires `bridge git import` to
/// have already run (needs the SHA map sidecar).
/// Requires the `ingest` feature (on by default).
#[cfg(feature = "ingest")]
Reason {
/// Source git repository the transcripts are about.
#[arg(long)]
path: std::path::PathBuf,
/// Cap candidates per commit. Higher = more coverage at the cost
/// of cross-attribution. Default tuned for typical dogfood runs.
#[arg(long, default_value_t = 5)]
max_sessions_per_commit: usize,
/// Drop sessions below this confidence (file-overlap × 0.65 +
/// time-fit × 0.25 + provider-hint × 0.10). Below ~0.20 the
/// matcher trips false positives; above ~0.50 it filters out
/// borderline-but-correct matches.
#[arg(long, default_value_t = 0.20)]
min_match_confidence: f32,
/// Limit how many commits the reason pass walks. Useful while
/// tuning extraction knobs against a small recent window.
#[arg(long)]
limit: Option<usize>,
/// Override the Claude transcript store. Empty string disables.
#[arg(long = "claude-home")]
claude_home: Option<String>,
/// Override the Codex transcript store. Empty string disables.
#[arg(long = "codex-home")]
codex_home: Option<String>,
/// Override the OpenCode data dir. Empty string disables.
#[arg(long = "opencode-home")]
opencode_home: Option<String>,
/// Don't write annotations — just report what would happen.
#[arg(long)]
dry_run: bool,
},
}