1use std::fs;
2use std::path::{Path, PathBuf};
3
4use commands::{CommandManifestEntry, CommandRegistry, CommandSource};
5use runtime::{BootstrapPhase, BootstrapPlan};
6use tools::{ToolManifestEntry, ToolRegistry, ToolSource};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct UpstreamPaths {
10 repo_root: PathBuf,
11}
12
13impl UpstreamPaths {
14 #[must_use]
15 pub fn from_repo_root(repo_root: impl Into<PathBuf>) -> Self {
16 Self {
17 repo_root: repo_root.into(),
18 }
19 }
20
21 #[must_use]
22 pub fn from_workspace_dir(workspace_dir: impl AsRef<Path>) -> Self {
23 let workspace_dir = workspace_dir
24 .as_ref()
25 .canonicalize()
26 .unwrap_or_else(|_| workspace_dir.as_ref().to_path_buf());
27 let primary_repo_root = workspace_dir
28 .parent()
29 .map_or_else(|| PathBuf::from(".."), Path::to_path_buf);
30 let repo_root = resolve_upstream_repo_root(&primary_repo_root);
31 Self { repo_root }
32 }
33
34 #[must_use]
35 pub fn commands_path(&self) -> PathBuf {
36 self.repo_root.join("src/commands.ts")
37 }
38
39 #[must_use]
40 pub fn tools_path(&self) -> PathBuf {
41 self.repo_root.join("src/tools.ts")
42 }
43
44 #[must_use]
45 pub fn cli_path(&self) -> PathBuf {
46 self.repo_root.join("src/entrypoints/cli.tsx")
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct ExtractedManifest {
52 pub commands: CommandRegistry,
53 pub tools: ToolRegistry,
54 pub bootstrap: BootstrapPlan,
55}
56
57fn resolve_upstream_repo_root(primary_repo_root: &Path) -> PathBuf {
58 let candidates = upstream_repo_candidates(primary_repo_root);
59 candidates
60 .into_iter()
61 .find(|candidate| candidate.join("src/commands.ts").is_file())
62 .unwrap_or_else(|| primary_repo_root.to_path_buf())
63}
64
65fn upstream_repo_candidates(primary_repo_root: &Path) -> Vec<PathBuf> {
66 let mut candidates = vec![primary_repo_root.to_path_buf()];
67
68 if let Some(explicit) = std::env::var_os("CLAUDE_CODE_UPSTREAM") {
69 candidates.push(PathBuf::from(explicit));
70 }
71
72 for ancestor in primary_repo_root.ancestors().take(4) {
73 candidates.push(ancestor.join("claw-code"));
74 candidates.push(ancestor.join("clawd-code"));
75 }
76
77 candidates.push(
78 primary_repo_root
79 .join("reference-source")
80 .join("claw-code"),
81 );
82 candidates.push(primary_repo_root.join("vendor").join("claw-code"));
83
84 let mut deduped = Vec::new();
85 for candidate in candidates {
86 if !deduped.iter().any(|seen: &PathBuf| seen == &candidate) {
87 deduped.push(candidate);
88 }
89 }
90 deduped
91}
92
93pub fn extract_manifest(paths: &UpstreamPaths) -> std::io::Result<ExtractedManifest> {
94 let commands_source = fs::read_to_string(paths.commands_path())?;
95 let tools_source = fs::read_to_string(paths.tools_path())?;
96 let cli_source = fs::read_to_string(paths.cli_path())?;
97
98 Ok(ExtractedManifest {
99 commands: extract_commands(&commands_source),
100 tools: extract_tools(&tools_source),
101 bootstrap: extract_bootstrap_plan(&cli_source),
102 })
103}
104
105#[must_use]
106pub fn extract_commands(source: &str) -> CommandRegistry {
107 let mut entries = Vec::new();
108 let mut in_internal_block = false;
109
110 for raw_line in source.lines() {
111 let line = raw_line.trim();
112
113 if line.starts_with("export const INTERNAL_ONLY_COMMANDS = [") {
114 in_internal_block = true;
115 continue;
116 }
117
118 if in_internal_block {
119 if line.starts_with(']') {
120 in_internal_block = false;
121 continue;
122 }
123 if let Some(name) = first_identifier(line) {
124 entries.push(CommandManifestEntry {
125 name,
126 source: CommandSource::InternalOnly,
127 });
128 }
129 continue;
130 }
131
132 if line.starts_with("import ") {
133 for imported in imported_symbols(line) {
134 entries.push(CommandManifestEntry {
135 name: imported,
136 source: CommandSource::Builtin,
137 });
138 }
139 }
140
141 if line.contains("feature('") && line.contains("./commands/") {
142 if let Some(name) = first_assignment_identifier(line) {
143 entries.push(CommandManifestEntry {
144 name,
145 source: CommandSource::FeatureGated,
146 });
147 }
148 }
149 }
150
151 dedupe_commands(entries)
152}
153
154#[must_use]
155pub fn extract_tools(source: &str) -> ToolRegistry {
156 let mut entries = Vec::new();
157
158 for raw_line in source.lines() {
159 let line = raw_line.trim();
160 if line.starts_with("import ") && line.contains("./tools/") {
161 for imported in imported_symbols(line) {
162 if imported.ends_with("Tool") {
163 entries.push(ToolManifestEntry {
164 name: imported,
165 source: ToolSource::Base,
166 });
167 }
168 }
169 }
170
171 if line.contains("feature('") && line.contains("Tool") {
172 if let Some(name) = first_assignment_identifier(line) {
173 if name.ends_with("Tool") || name.ends_with("Tools") {
174 entries.push(ToolManifestEntry {
175 name,
176 source: ToolSource::Conditional,
177 });
178 }
179 }
180 }
181 }
182
183 dedupe_tools(entries)
184}
185
186#[must_use]
187pub fn extract_bootstrap_plan(source: &str) -> BootstrapPlan {
188 let mut phases = vec![BootstrapPhase::CliEntry];
189
190 if source.contains("--version") {
191 phases.push(BootstrapPhase::FastPathVersion);
192 }
193 if source.contains("startupProfiler") {
194 phases.push(BootstrapPhase::StartupProfiler);
195 }
196 if source.contains("--dump-system-prompt") {
197 phases.push(BootstrapPhase::SystemPromptFastPath);
198 }
199 if source.contains("--ternlang-in-chrome-mcp") {
200 phases.push(BootstrapPhase::ChromeMcpFastPath);
201 }
202 if source.contains("--daemon-worker") {
203 phases.push(BootstrapPhase::DaemonWorkerFastPath);
204 }
205 if source.contains("remote-control") {
206 phases.push(BootstrapPhase::BridgeFastPath);
207 }
208 if source.contains("args[0] === 'daemon'") {
209 phases.push(BootstrapPhase::DaemonFastPath);
210 }
211 if source.contains("args[0] === 'ps'") || source.contains("args.includes('--bg')") {
212 phases.push(BootstrapPhase::BackgroundSessionFastPath);
213 }
214 if source.contains("args[0] === 'new' || args[0] === 'list' || args[0] === 'reply'") {
215 phases.push(BootstrapPhase::TemplateFastPath);
216 }
217 if source.contains("environment-runner") {
218 phases.push(BootstrapPhase::EnvironmentRunnerFastPath);
219 }
220 phases.push(BootstrapPhase::MainRuntime);
221
222 BootstrapPlan::from_phases(phases)
223}
224
225fn imported_symbols(line: &str) -> Vec<String> {
226 let Some(after_import) = line.strip_prefix("import ") else {
227 return Vec::new();
228 };
229
230 let before_from = after_import
231 .split(" from ")
232 .next()
233 .unwrap_or_default()
234 .trim();
235 if before_from.starts_with('{') {
236 return before_from
237 .trim_matches(|c| c == '{' || c == '}')
238 .split(',')
239 .filter_map(|part| {
240 let trimmed = part.trim();
241 if trimmed.is_empty() {
242 return None;
243 }
244 Some(trimmed.split_whitespace().next()?.to_string())
245 })
246 .collect();
247 }
248
249 let first = before_from.split(',').next().unwrap_or_default().trim();
250 if first.is_empty() {
251 Vec::new()
252 } else {
253 vec![first.to_string()]
254 }
255}
256
257fn first_assignment_identifier(line: &str) -> Option<String> {
258 let trimmed = line.trim_start();
259 let candidate = trimmed.split('=').next()?.trim();
260 first_identifier(candidate)
261}
262
263fn first_identifier(line: &str) -> Option<String> {
264 let mut out = String::new();
265 for ch in line.chars() {
266 if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' {
267 out.push(ch);
268 } else if !out.is_empty() {
269 break;
270 }
271 }
272 (!out.is_empty()).then_some(out)
273}
274
275fn dedupe_commands(entries: Vec<CommandManifestEntry>) -> CommandRegistry {
276 let mut deduped = Vec::new();
277 for entry in entries {
278 let exists = deduped.iter().any(|seen: &CommandManifestEntry| {
279 seen.name == entry.name && seen.source == entry.source
280 });
281 if !exists {
282 deduped.push(entry);
283 }
284 }
285 CommandRegistry::new(deduped)
286}
287
288fn dedupe_tools(entries: Vec<ToolManifestEntry>) -> ToolRegistry {
289 let mut deduped = Vec::new();
290 for entry in entries {
291 let exists = deduped
292 .iter()
293 .any(|seen: &ToolManifestEntry| seen.name == entry.name && seen.source == entry.source);
294 if !exists {
295 deduped.push(entry);
296 }
297 }
298 ToolRegistry::new(deduped)
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn fixture_paths() -> UpstreamPaths {
306 let workspace_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
307 UpstreamPaths::from_workspace_dir(workspace_dir)
308 }
309
310 fn has_upstream_fixture(paths: &UpstreamPaths) -> bool {
311 paths.commands_path().is_file()
312 && paths.tools_path().is_file()
313 && paths.cli_path().is_file()
314 }
315
316 #[test]
317 fn extracts_non_empty_manifests_from_upstream_repo() {
318 let paths = fixture_paths();
319 if !has_upstream_fixture(&paths) {
320 return;
321 }
322 let manifest = extract_manifest(&paths).expect("manifest should load");
323 assert!(!manifest.commands.entries().is_empty());
324 assert!(!manifest.tools.entries().is_empty());
325 assert!(!manifest.bootstrap.phases().is_empty());
326 }
327
328 #[test]
329 fn detects_known_upstream_command_symbols() {
330 let paths = fixture_paths();
331 if !paths.commands_path().is_file() {
332 return;
333 }
334 let commands =
335 extract_commands(&fs::read_to_string(paths.commands_path()).expect("commands.ts"));
336 let names: Vec<_> = commands
337 .entries()
338 .iter()
339 .map(|entry| entry.name.as_str())
340 .collect();
341 assert!(names.contains(&"addDir"));
342 assert!(names.contains(&"review"));
343 assert!(!names.contains(&"INTERNAL_ONLY_COMMANDS"));
344 }
345
346 #[test]
347 fn detects_known_upstream_tool_symbols() {
348 let paths = fixture_paths();
349 if !paths.tools_path().is_file() {
350 return;
351 }
352 let tools = extract_tools(&fs::read_to_string(paths.tools_path()).expect("tools.ts"));
353 let names: Vec<_> = tools
354 .entries()
355 .iter()
356 .map(|entry| entry.name.as_str())
357 .collect();
358 assert!(names.contains(&"AgentTool"));
359 assert!(names.contains(&"BashTool"));
360 }
361}